C++基础:模拟实现vector(有存在深层次的浅拷贝问题)

目录

引言 

一、vector的基本框架

二、尾插push_back、reserve扩容、任意位置插入insert(增)

1.reserve扩容

2.push_back尾插

3.深层次的浅拷贝问题

4. 任意位置插入数据insert(会使迭代器失效)

三、构造、析构、拷贝构造函数

1.构造函数

1.1无参构造

1.2initializer_list 类作为参数来构造。

1.3用任意类型的迭代器来构造

2.拷贝构造

3.析构函数

四、判空函数(empty)、尾删(pop_back)、删除任意位置元素(erase)

1.判空

2.尾删

3.删除任意位置元素(会使迭代器失效)

五、[]运算符重载、=赋值运算符重载、交换vector函数

1.swap函数

2.=赋值运算符重载

3.[]运算符重载

六、vector.h代码


引言 

手把手带你实现vector

实现vector的意义:对底层有个更深的理解。锻炼代码能力。

_涂色_-博客主页

C++基础专栏

为了与STL中的vector区分开,这里都写在命名空间stl中,并且实现类函数的声明和定义分离。 

分3个文件来写:

  • string.h:string类的声明。
  • text.c :测试string的接口是否正确。

因为:模板声明和定义不能分离定义到两个文件。所以都在h .cpp文件中。

一、vector的基本框架

这里是为了保持和STL源码的私有成员一样所以这样设计。

这里的迭代器只是以简单的形式来实现的,真实的迭代器不是这样的。

namespace stl
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;const_iterator begin() const //vector开始位置{return _start;}const_iterator end() const  //vector结束位置{return _finish;}iterator begin(){return _start;}iterator end(){return _finish;}int capacity() const  //vector里面的个数{return _endofstorage - _start;}int size() const  //计算出vector数组中元素个数{return _finish - _start;}private:iterator _start = nullptr;  // 存储数据的数组iterator _finish = nullptr;  //数组结尾的写一个位置iterator _endofstorage = nullptr;  //数组的空间的最后一个位置};
}

二、尾插push_back、reserve扩容、任意位置插入insert(增)

1.reserve扩容

先实现reserve扩容

注意:必须先计算出原来的元素个数,若在下面计算元素个数的时候,由于_start已经发生了改变,会使得计算元素个数错误。

        关于深层次的浅拷贝问题,写出push_back来给出说明。

void reserve(const size_t n)
{if (n > capacity()){size_t old_size = size();T* tmp = new T[n];//拷贝旧空间数据到新空间数据if (_start){//memcpy(tmp, _start, sizeof(T*) * old_size); 会存在深层次的浅拷贝问题。for (size_t i = 0; i < old_size; i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + old_size;_endofstorage = _start + n;}
}

2.push_back尾插

先看空间够不够,不够就先扩容,然后尾插即可。

void push_back(const T& x)
{if (_finish == _endofstorage){reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;
}

3.深层次的浅拷贝问题

运行下面代码,当尾插5个string类型的数据时,由于需要扩容,就需要拷贝原理的数据。

namespace stl
{	template<class container>void Print(const container& con){for (auto e : con){cout << e << " ";}cout << endl;}void test_vector1(){vector<string> v1;v1.push_back("11111111111111111111111");v1.push_back("11111111111111111111111");v1.push_back("11111111111111111111111");v1.push_back("11111111111111111111111");v1.push_back("11111111111111111111111");Print(v1);}
}int main()
{stl::test_vector1();return 0;
}

但是,如果使用memcpy拷贝数据的话,就会形成生层次的浅拷贝问题。

当使用memcpy拷贝时:

 所以这里不能使用memcpy来进行拷贝,要通过赋值操作符,调用函数的拷贝构造,来进行拷贝。

4. 任意位置插入数据insert(会使迭代器失效)

在 C++ 里,迭代器失效是一个重要概念,它指的是由于容器结构发生改变,导致原本指向容器中某个元素的迭代器不再有效。此时若继续使用这个迭代器,可能会引发程序崩溃、得到错误结果等严重问题。

  1. 先看空间够不够,不够的话就先扩容,扩容的时候,需要更新pos指针,否则pos位置就被释放了(pos就是野指针了)。
  2. 将pos位置和后面的元素向后移动一位
  3. 最后在pos位置插入元素x
iterator insert(iterator pos, const T& x) //给返回值是解决迭代器失效
{assert(pos >= _start);assert(pos <= _finish);if (_finish == _endofstorage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);// 扩容后这里需要更新pos,否则pos就是野指针了pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;return pos;
}

三、构造、析构、拷贝构造函数

1.构造函数

1.1无参构造

可以手动写,但是没有必要,因为成员给了缺省值,所以,不带惨的构造使用编译器生成的即可。

	vector():_start = nullptr,_finish = nullptr, _endofstorage = nullptr{}

但是下面会写拷贝构造,所以编译器不会自己生成构造函数了,所以:

		// 强制编译器生成默认构造vector() = default;

1.2initializer_list<T> 类作为参数来构造。

使用 initializer_list<T> 类作为参数来构造。

vector(initializer_list<T> il)
{reserve(il.size());for (auto& e : il){push_back(e);}
}

通过这种构造,可以这样初始化vector:

vector<int> v1 = { 1,2,3,4,5,5 };
vector<int> v2 = { 1,2,3,4,5,51,1,1,1,1,1,1,1 };

原因就是通过 initializer_list<T>类来进行初始化。

1.3用任意类型的迭代器来构造

当我们想用string的一段,来初始化vector时:

		// 函数模板// 任意类型容器迭代器初始化template <class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}

运行下面这段代码 

string s1("hello world");
vector<int> v6(s1.begin(), s1.end());
Print(v6);

 

2.拷贝构造

直接开一样的空间,进行尾插来拷贝构造

		//v2(v)vector(const vector<T>& v){reserve(v.capacity());for (auto& e : v){push_back(e);}}

3.析构函数

 释放数组,然后都置为空。

		~vector<T>(){if (_start){delete[] _start;_start = _finish = _endofstorage = nullptr;}}

四、判空函数(empty)、尾删(pop_back)、删除任意位置元素(erase)

1.判空

  • 是空,就返回真,
  • 不是空,就返回假
bool empty() const
{return _start == _finish;
}

2.尾删

有了判空函数,就不需要使用assert来断言了。

void pop_back()
{//assert(_finish > _start);assert(!empty());--_finish;}

3.删除任意位置元素(会使迭代器失效)

iterator erase(const iterator pos)  //给返回值是解决迭代器失效
{assert(pos >= _start);assert(pos < _finish);iterator it = pos + 1;while (it != _finish){*(it - 1) = *it;++it;}return pos;
}

五、[]运算符重载、=赋值运算符重载、交换vector函数

1.swap函数

交换两个vector

void swap(vector<T> v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);
}

2.=赋值运算符重载

tmp是v2的拷贝,然后和v1直接交换,就使得v1 等于 v2了。

// V1 = V2
vector<int>& operator=(vector<T> tmp)
{swap(tmp);return *this;
}

3.[]运算符重载

可以通过下标来访问vector了

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

六、vector.h代码

#pragma once
#include<iostream>
#include<assert.h>
#include<vector>
using namespace std;
// 模板 声明和定义不能分离定义到两个文件.h .cppnamespace stl
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}iterator begin(){return _start;}iterator end(){return _finish;}// 强制编译器生成默认构造vector() = default;vector(initializer_list<T> il){reserve(il.size());for (auto& e : il){push_back(e);}}//v2(v)vector(const vector<T>& v){reserve(v.capacity());for (auto& e : v){push_back(e);}}~vector<T>(){if (_start){delete[] _start;_start = _finish = _endofstorage = nullptr;}}// V1 = V2vector<int>& operator=(vector<T> tmp){swap(tmp);return *this;}// 函数模板// 任意类型容器迭代器初始化template <class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}/*vector(iterator first, iterator last){while (first != last){push_back(*first);++first;}}*/vector(int n, int val = T()){resize(n, val);}vector(size_t n, T val = T()){resize(n, val);}void swap(vector<T> v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}T& operator[](size_t i){assert(i < size());return _start[i];}const T& operator[](size_t i) const{assert(i < size());return _start[i];}void reserve(const size_t n){if (n > capacity()){size_t old_size = size();T* tmp = new T[n];//拷贝旧空间数据到新空间数据if (_start){//memcpy(tmp, _start, sizeof(T*) * old_size); 会存在深层次的浅拷贝问题。for (size_t i = 0; i < old_size; i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + old_size;_endofstorage = _start + n;}}int capacity() const{return _endofstorage - _start;}int size() const{return _finish - _start;}void push_back(const T& x){if (_finish == _endofstorage){reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;}void pop_back(){//assert(_finish > _start);assert(!empty());--_finish;}iterator insert(iterator pos, const T& x) //给返回值是解决迭代器失效{assert(pos >= _start);assert(pos <= _finish);if (_finish == _endofstorage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);// 扩容后这里需要更新pos,否则pos就是野指针了pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;return pos;}iterator erase(const iterator pos)  //给返回值是解决迭代器失效{assert(pos >= _start);assert(pos < _finish);iterator it = pos + 1;while (it != _finish){*(it - 1) = *it;++it;}return pos;}bool empty() const{return _start == _finish;}void resize(size_t n, T val = T()){if (n > size()){reserve(n);while (_finish != _start + n){*_finish = val;++_finish;}}else{_finish = _start + n;}}private:iterator _start = nullptr;iterator _finish = nullptr;iterator _endofstorage = nullptr;};
}

完。

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

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

相关文章

【力扣】关于链表索引

怎么才能走到目标节点呢&#xff1f; 从9走到2&#xff0c;需要2步&#xff0c;他们的索引分别是&#xff1a;0&#xff0c;2 在for循环里&#xff1a;int i 0; i < 2; i i的范围是【0&#xff0c;2&#xff09; 有&#xff1a;2 2 - 0 如果从虚拟头节点开始走到2&#x…

C++ ODB框架详解:现代C++对象关系映射解决方案

目录 框架简介安装与配置基础概念实体映射数据库操作查询操作高级功能性能优化最佳实践 框架简介 ODB&#xff08;Object-Relational Database&#xff09;是一个专为C设计的对象关系映射&#xff08;ORM&#xff09;框架&#xff0c;由CodeSynthesis公司开发。它提供了一种…

Ai书签管理工具开发全记录(一):项目总览与技术蓝图

文章目录 Ai书签管理工具开发全记录&#xff08;一&#xff09;&#xff1a;项目总览与技术蓝图 ✨1. 项目背景与核心价值 &#x1f4a1;1.1. 核心特点 2. 技术架构分析 &#x1f3d7;️功能架构全景图典型工作流 3. 核心技术栈选择 &#x1f6e0;️4. 预期使用功能说明 &#…

GUI 编程——python

GUI 编程核心概念 GUI&#xff08;图形用户界面&#xff0c;Graphical User Interface&#xff09; 是一种通过图形元素&#xff08;窗口、按钮、菜单等&#xff09;与用户交互的应用程序形式&#xff0c;相比命令行界面更直观易用。以下是学习 GUI 编程的基础概念和流程&…

【Doris基础】Apache Doris 基本架构深度解析:从存储到查询的完整技术演进

目录 1 引言 2 Doris 架构全景图 2 核心组件技术解析 2.1 Frontend 层&#xff08;FE&#xff09; 2.2 Backend 层&#xff08;BE&#xff09; 3 数据存储与复制机制 3.1 存储架构演进 3.2 副本复制策略 4 查询处理全流程解析 4.1 查询生命周期 5 高可用设计 5.1 F…

光电赋能低空场景,灵途科技助力无人机持续升级

2025 UASE 主题为“步入低空经济新时代”的“2025第九届世界无人机大会暨国际低空经济与无人系统博览会/第十届深圳国际无人机展览会”5月23日在深圳会展中心隆重开幕。本届展会汇聚了全球800余家企业参展&#xff0c;展示5000多款无人机及系统设备&#xff0c;全面呈现低空经…

iOS QQ抽屉式导航的实现

QQ个人中心的侧滑功能(通常称为"抽屉式导航")可以通过以下几种方式在iOS中实现&#xff1a; 主要实现方案 使用第三方库 最快速的方式是使用成熟的第三方库&#xff1a; SWRevealViewController&#xff1a;最流行的侧滑菜单库MMDrawerController&#xff1a;另一…

【Pandas】pandas DataFrame drop

Pandas2.2 DataFrame Reindexing selection label manipulation 方法描述DataFrame.add_prefix(prefix[, axis])用于在 DataFrame 的行标签或列标签前添加指定前缀的方法DataFrame.add_suffix(suffix[, axis])用于在 DataFrame 的行标签或列标签后添加指定后缀的方法DataFram…

长短期记忆网络 (LSTM) 详解:从原理到应用

一、引言&#xff1a;序列数据处理的挑战​ 在自然语言处理、语音识别、时间序列分析等领域&#xff0c;数据通常以序列形式存在&#xff0c;前后数据点之间存在依赖关系。传统循环神经网络 (RNN) 虽然能捕捉序列依赖&#xff0c;但存在严重的梯度消失 / 爆炸问题&#xff0c;…

三天掌握PyTorch精髓:从感知机到ResNet的快速进阶方法论

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 一、分析式AI基础与深度学习核心概念 1.1 深度学习三要素 数学基础&#xff1a; f(x;W,b)σ(Wxb)(单层感知机) 1.2 PyTorch核心组件 张量操作示例…

Linux操作系统概述

一、操作系统的作用 1、五大基本功能 &#xff08;1&#xff09;进程和线程的管理&#xff1a;进程线程的状态、控制、同步互斥、通信调度等 (2&#xff09;存储管理&#xff1a;分配/回收、地址转换、存储保护等 (3&#xff09;文件管理&#xff1a;文件目录、文件操作、磁盘…

Python爬虫第22节- 结合Selenium识别滑动验证码实战

目录 一、引言 二、滑动验证码原理与反爬机制 2.1 验证码原理 2.2 反爬机制 三、工程实战&#xff1a;滑动验证码识别全流程 3.1 工程准备 3.1.1 环境依赖 3.1.2 目标网站与验证码识别案例 3.2 核心破解流程 3.2.1 自动化打开网页与登录 3.2.2 获取验证码图片&#…

NSSCTF-[NISACTF 2022]huaji?

下载附件得到文件 放到kali里面看看 发现是一张图片 用binwalk命令对其进行分离 发现需要密码 用010打开图片进行查看 对其进行解密 分别得到 ctf_NISA_2022 nisa_2022 发现ctf_NISA_2022是密码 得到flag NSSCTF{Nls_FumYEnnOjy}

nt!CcGetVacbMiss函数分析之设置好nt!_VACB然后调用函数nt!SetVacb

第一部分&#xff1a;MmMapViewInSystemCache函数返回 Status MmMapViewInSystemCache (SharedCacheMap->Section, &Vacb->BaseAddress, &NormalOffset, …

Uniapp+UView+Uni-star打包小程序极简方案

一、减少主包体积 主包污染源&#xff08;全局文件依赖&#xff09;劲量独立导入 componentsstaticmain.jsApp.vueuni.css 分包配置缺陷&#xff0c;未配置manifest.json中mp-weixin节点 "usingComponents" : true,"lazyCodeLoading" : "requiredC…

Teigha应用——解析CAD文件(DWG格式)Teigha在CAD C#二次开发中的基本应用

Teigha是一款专为开发者设计的工具&#xff0c;其核心技术在于强大的API和丰富的功能集&#xff0c;提供了一系列工具和方法&#xff0c;使开发者能够轻松地读取、解析和操作DWG文件。它支持多种操作系统&#xff0c;能在处理大型DWG文件时保持高效性能&#xff0c;还可用于构建…

JavaWeb:SpringBoot Bean管理

获取Bean Bean作用域 解决循环依赖方式 1.粗暴删除依赖 2.打破依赖配置 3.使用lazy注解 引入第三方Bean

Lua 脚本在 Redis 中的运用-23(Lua 脚本语法教程)

在 Redis 中编写和执行 Lua 脚本 Lua 脚本是在 Redis 中执行自定义逻辑的强大功能&#xff0c;可以直接在 Redis 服务器上执行。这减少了延迟&#xff0c;提高了性能&#xff0c;并能够实现客户端脚本难以或不可能实现的原子操作。通过在 Redis 中嵌入 Lua 脚本&#xff0c;您…

从零实现本地语音识别(FunASR)

FunASR 是达摩院开源的综合性语音处理工具包&#xff0c;提供语音识别&#xff08;ASR&#xff09;、语音活动检测&#xff08;VAD&#xff09;、标点恢复&#xff08;PUNC&#xff09;等全流程功能&#xff0c;支持多种主流模型&#xff08;如 Paraformer、Whisper、SenseVoic…

deepseek开源资料汇总

参考&#xff1a;DeepSeek“开源周”收官&#xff0c;连续五天到底都发布了什么? 目录 一、首日开源-FlashMLA 二、Day2 DeepEP 三、Day3 DeepGEMM 四、Day4 DualPipe & EPLB 五、Day5 3FS & Smallpond 总结 一、首日开源-FlashMLA 多头部潜在注意力机制&#x…