当前位置: 首页 > news >正文

C++之打造my vector篇

目录

前言

1.参照官版,打造vector的基本框架

2.丰富框架,实现接口方法

基本的迭代器实现

数据的[]访问

容量和数据空间的改变

vector空间大小的返回与判空

数据的增删

数据打印

拷贝构造和赋值重载

3.扩展延伸,深度理解代码

迭代器失效问题

使用memcpy的拷贝问题

结束语


前言

前面章节我们讲解了vector相关接口,方法的使用,本节内容我们将自己创造vector,模仿官方的接口方法。

1.参照官版,打造vector的基本框架

通过查看官方文档我们知道,vector是个可以变化的数组,是个容器,可以储存一系列数据,

是典型的模版类。

且有三个基本成员start,finish,end_of_storage,我们可以理解为指向数组的开端,数据的结尾,以及容量的结束指针。

上图为插入成员后的分布情况。

故创造了一下的基本框架,因为是我们自己的实现,所以定义了一个my_vector的命名空间,

namespace my_vector {template<class T>class vector {public:typedef T* iterator;typedef const T* const_iterator;vector() {}~vector() {if (_start) {delete[]_start;_start = _finish = _end_of_storage = nullptr;}
}
private:iterator _start = nullptr;iterator _finish=nullptr;iterator _end_of_storage=nullptr;
};

这里我们采用初始化列表来进行默认构造,直接使用私有成员的缺省值,较为简便。

C++11前置生成默认构造

vector()=default;

2.丰富框架,实现接口方法

基本的迭代器实现

iterator begin() {return _start;
}
iterator end() {return _finish;
}
const_iterator begin()const {return _start;
}
const_iterator end() const{return _finish;
}

就是比较简单的返回开始和结束的指针。

数据的[]访问

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(size_t n) {if (n > capacity()) {size_t oldsize = size();T* temp = new T[n];memcpy(tmp, _start, old_size * sizeof(T));
/*for (size_t i = 0; i < oldsize; i++) {temp[i] = _start[i];}
*/delete[]_start;_start = temp;_finish =temp + oldsize;_end_of_storage = temp+n;}
}

对于扩容操作,我们创建了一个新的数组,然后定义一个oldsize来存储以前的数据空间,来确定之后的_finish位置,因为我们在之前释放了原来的数组了,如果不想这样操作,不想定义一个oldsize,就要把delete操作放在_finish=temp+size()之后。

这里用了memcpy函数来转移数据,但是我们会发现有一个小小的问题,当数据类型为string时,程序会崩溃,这个后续会讲解的。

void resize(size_t n, T val = T()){if (n < size()){_finish = _start + n;}else{reserve(n);while (_finish < _start + n){*_finish = val;++_finish;}}}

resize函数的作用是调整容器的大小,使其能够容纳n个元素。如果n小于当前容器的大小,则容器会被截断;如果n大于当前容器的大小,则容器会被扩展,并且新增的元素会被初始化为val的值。

T val = T():表示一个默认参数,它是容器的元素类型T的默认构造函数生成的对象。如果调用resize时没有指定这个参数,就会使用元素类型的默认值。

如果n小于当前容器的大小
_finish = _start + n;:这条语句会截断容器,使其大小变为n。这里_start是指向容器第一个元素的指针,_finish是指向容器最后一个元素的下一个位置的指针。通过将`_finish`向前移动到_start + n的位置,容器的大小就被减少了。
 如果`n`大于或等于当前容器的大小
- reserve(n);:调用reserve函数确保容器的容量至少为n。如果当前容量小于n,reserve会重新分配内存以容纳至少n个元素。
- 默认构造函数`T()`必须存在,以便能够为新元素提供默认值。如果`T`没有默认构造函数,则这段代码在尝试使用默认参数时会出错。

vector空间大小的返回与判空

size_t size()const {return _finish - _start;
}
size_t capacity()const {return _end_of_storage - _start;
}
bool empty()const {return _start == _finish;
}

大小返回就是几个指针加减即可

数据的增删

对于数据的增删,模拟尾插,尾删,指定位置的插入,删除(后两者都与迭代器iterator相结合)

void push_back(const T& x) {if (_finish == _end_of_storage) {reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;
}
void pop_back() {assert(!empty());--_finish;
}
void erase(iterator pos) {assert(pos >= _start);assert(pos <= _finish);iterator it = pos + 1;while (it != end()) {*(it - 1) = *it;it++;}_finish--;
}
iterator insert(iterator pos,const T&x) {assert(pos >= _start  && pos<=_finish);if (_finish == _end_of_storage) {size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;}iterator end = _finish + 1;while (end > pos) {*end = *(end - 1);end--;}*pos = x;_finish++;return pos;
}

对于插入数据的操作都要进行扩容的判断操作,对于数据的挪动我们可以采用依次赋值,就像代码中的*end=*(end-1);end--//*(it-1)=*it;it++;

通过画图可以更加清楚的理解。

数据打印

template<class T>
void print_vector(const vector<T>& v) {//typename vector<T>::const_iterator it = v.begin();auto it = v.begin();while (it != v.end()) {cout << *it << " ";it++;}cout << '\n';for  (auto e:v) {cout << e << " ";}cout << endl;
}
template<class Container>
void print_container(const Container& v) {auto it = v.begin();while (it != v.end()) {cout << *it << " ";it++;}cout << '\n';for (auto e : v) {cout << e << " ";}cout << endl;
}

规定,没有实例化的类模板里面取东西,编译器不能区分这里const_iterator是类型还是静态成员变量,所以在注释的部分我们看见前面加了个typename.

//typename vector<T>::const_iterator it = v.begin();

print_vector 函数专门用于打印 std::vector<T> 类型的容器。

print_container 函数是一个更通用的模板函数,可以用于打印任何符合容器概念的类型。

 

void vector5()
{vector<string> v;v.push_back("11111111111111111111");v.push_back("11111111111111111111");v.push_back("11111111111111111111");v.push_back("11111111111111111111");print_container(v);v.push_back("11111111111111111111");print_container(v);
}

拷贝构造和赋值重载

vector(const vector<T>& v) {
reserve(v.size());
for (auto e : v) {push_back(e);
}
}
vector<T>operator=(const vector<T>& v) {
if (this != v) {reserve(v.size());for (auto e : v) {push_back(e);}
}
return this;}

这一种思路是开设新空间,然后将数据一个个尾插到创建的对象中。

void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);}// v1 = v3//vector& operator=(vector v)vector<T>& operator=(vector<T> v){swap(v);return *this;}

这种思路是交换的方式,通过调用官方库中的函数,指针交换,将v的地址给了创建的对象。

template <class InputIterator>
vector(InputIterator first, InputIterator last) {while (first != last) {push_back(*first);first++;}
}

通过传需要拷贝的对象的数据范围给新对象(迭代器区间),是个函数模版,可以用任意的迭代器初始化,类型匹配即可 。

模板构造函数,用于构造一个std::vector,该构造函数接受两个迭代器firstlast,它们定义了要复制到新vector中的元素的范围。

3.扩展延伸,深度理解代码

在VS环境下,比较严格,在迭代器方面比较严格,特别是失效迭代器的访问。

迭代器失效问题

在测试接口的过程中,有个bug就是迭代器失效问题

我们知道迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T*

因此迭代器失效,实际就是迭代器 底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即 如果继续使用已经失效的迭代器,程序可能会崩溃)。

1.对vector进行扩容操作,像resize,reserve等操作

还有就是在insert,push_back操作过程中涉及了扩容

#include <iostream>
using namespace std;
#include <vector>
int main()
{vector<int> v{ 1,2,3,4,5,6 };auto it = v.begin();// 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容// v.resize(100, 8);// reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容//量改变v.reserve(100);// 插入元素期间,可能会引起扩容,而导致原空间被释放v.insert(v.begin(), 0);v.push_back(8);// 给vector重新赋值,可能会引起底层容量改变//v.assign(100, 8);/*出错原因:以上操作,都有可能会导致vector扩容,也就是说vector底层原理旧空间被释放掉,而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的空间,而引起代码运行时崩溃。解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新赋值即可。*/while (it != v.end()){cout << *it << " ";++it;}cout << endl;return 0;
}

修改后的代码 

#include <iostream>
#include <vector>
using namespace std;int main()
{vector<int> v{ 1,2,3,4,5,6 };// 在修改vector之后,重新获取迭代器auto it = v.begin();v.reserve(100);v.insert(v.begin(), 0);v.push_back(8);// 如果使用v.assign(100, 8);,也需要在之后重新获取迭代器// 重新获取迭代器,因为之前的操作可能会改变vector的内存it = v.begin();while (it != v.end()){cout << *it << " ";++it;}cout << endl;return 0;
}

还有一点就是insert数据过后,即使没有扩容,指向容器中插入点之后的所有迭代器、指针和引用都可能失效。

所以当我们继续访问修改p的位置数据,已经失效了,需要更新失效的迭代器。

由于数据挪动,p的指向改变了,所以我们认为迭代器也失效了。

v.insert(p, 40);

p=v.insert(p, 40);
(*(p+1)) *= 10; 

 2.erase的删除导致的迭代器失效问题

	void test_vector3(){std::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);print_container(v);// 删除所有的偶数auto it = v.begin();while (it != v.end()){if (*it % 2 == 0){v.erase(it);}else{++it;}}print_container(v);}
}

当我们用VS std中的接口时,会发现直接报错

所以我们也要进行重新的更新

it=v.erase(it); 

#include <iostream>
using namespace std;
#include <vector>int main(){int a[] = { 1, 2, 3, 4 };vector<int> v(a, a + sizeof(a) / sizeof(int));// 使用find查找3所在位置的iteratorvector<int>::iterator pos = find(v.begin(), v.end(), 3);// 删除pos位置的数据,导致pos迭代器失效。v.erase(pos);cout << *pos << endl; // 此处会导致非法访问return 0;}

使用memcpy的拷贝问题

当我们想拷贝几个字符串时,就会出现问题了。

 问题分析:

1. memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中。

2. 如果拷贝的是自定义类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。

void reserve(size_t n) {if (n > capacity()) {size_t oldsize = size();T* temp = new T[n];memcpy(tmp, _start, old_size * sizeof(T));
/*for (size_t i = 0; i < oldsize; i++) {temp[i] = _start[i];}
*/delete[]_start;_start = temp;_finish =temp + oldsize;_end_of_storage = temp+n;}
}

当我们使用这个memcpy版本进行扩容插入时,程序会出现问题

测试代码

void vector5() {vector<string> v;v.push_back("11111111111111111111");v.push_back("11111111111111111111");v.push_back("11111111111111111111");v.push_back("11111111111111111111");print_container(v);v.push_back("11111111111111111111");print_container(v);
}

memcpy是浅拷贝,temp和原来的v指向了同一块空间,当调用了delete[]时,11111111...字符串被析构了,空间释放,变成随机值,后面又delete,free _start ,这时候temp指向的是释放的空间。

所以我们可以调用赋值,就可以解决问题,本质调用string的赋值,其他类型赋值一样的。

旧空间释放就不会影响新空间。

for (size_t i = 0; i < oldsize; i++) {
            temp[i] = _start[i];

4.完整代码复现

#pragma once
#include <iostream>
#include <assert.h>
#include <vector>
#include <string>
using namespace std;
namespace my_vector {template<class T>class vector {public:typedef T* iterator;typedef const T* const_iterator;vector(){}vector(const vector<T>& v) {reserve(v.size());for (auto e : v) {push_back(e);}}/*vector<T>operator=(const vector<T>& v) {if (this != v) {reserve(v.size());for (auto e : v) {push_back(e);}}return this;}*/template <class InputIterator>vector(InputIterator first, InputIterator last) {while (first != last) {push_back(*first);first++;}}void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);}// v1 = v3//vector& operator=(vector v)vector<T>& operator=(vector<T> v){swap(v);return *this;}void clear() {_finish = _start;}~vector() {if (_start) {delete[]_start;_start = _finish = _end_of_storage = nullptr;}}iterator begin() {return _start;}iterator end() {return _finish;}const_iterator begin()const {return _start;}const_iterator end() const {return _finish;}void reserve(size_t n) {if (n > capacity()) {size_t oldsize = size();T* temp = new T[n];memcpy(temp, _start, oldsize*sizeof(T));/*for (size_t i = 0; i < oldsize; i++) {temp[i] = _start[i];}*/delete[]_start;_start = temp;_finish = temp + oldsize;_end_of_storage = temp + n;}}void resize(size_t n, T val = T()) {if (n < size()) {_finish = _start + n;}else {reserve(n);while (_finish < _start + n) {*_finish = val;_finish++;}}}void push_back(const T& x) {if (_finish == _end_of_storage) {reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;}void pop_back() {assert(!empty());--_finish;}void erase(iterator pos) {assert(pos >= _start);assert(pos <= _finish);iterator it = pos + 1;while (it != end()) {*(it - 1) = *it;it++;}_finish--;}iterator insert(iterator pos, const T& x) {assert(pos >= _start && pos <= _finish);if (_finish == _end_of_storage) {size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;}iterator end = _finish + 1;while (end > pos) {*end = *(end - 1);end--;}*pos = x;_finish++;return pos;}size_t size()const {return _finish - _start;}size_t capacity()const {return _end_of_storage - _start;}bool empty()const {return _start == _finish;}T& operator[](size_t i) {assert(i < size());return _start[i];}const T& operator[](size_t i) const{assert(i < size());return _start[i];}private:iterator _start = nullptr;iterator _finish = nullptr;iterator _end_of_storage = nullptr;};template<class T>void print_vector(const vector<T>& v) {//typename vector<T>::const_iterator it = v.begin();auto it = v.begin();while (it != v.end()) {cout << *it << " ";it++;}cout << '\n';for (auto e : v) {cout << e << " ";}cout << endl;}template<class Container>void print_container(const Container& v) {/*auto it = v.begin();while (it != v.end()) {cout << *it << " ";it++;}cout << '\n';*/for (auto e : v) {cout << e << " ";}cout << endl;}void vector1() {vector<int>v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);int x;cin >> x;auto p = find(v.begin(), v.end(), x);if (p != v.end()) {p = v.insert(p, 40);(*(p + 1)) *= 10;}//v.pop_back();//v.pop_back();//v.insert(v.begin() + 2, 5);//v.erase(v.begin() + 3);for (size_t i = 0; i < v.size(); i++) {cout << v[i] << endl;}}void vector2() {vector<int>v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);print_vector(v);vector<int>v1(v);vector <int>v2 = v;vector<int> v3(v1.begin(), v1.begin() + 3);print_container(v1);print_container(v2);print_container(v3);/*vector<double>v1;v1.push_back(1.1);v1.push_back(2.2);v1.push_back(3.3);v1.push_back(4.4);v1.push_back(5.5);v1.push_back(6.6);print_container(v1);*/}void vector3() {vector<int> v;v.resize(10, 1);v.reserve(20);print_container(v);cout << v.size() << endl;cout << v.capacity() << endl;v.resize(15, 2);print_container(v);v.resize(25, 3);print_container(v);v.resize(5);print_container(v);}void vector4() {vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(4);v.push_back(5);print_container(v);// 删除所有的偶数auto it = v.begin();while (it != v.end()){if (*it % 2 == 0){v.erase(it);}else {//不加else,不会删除连续的偶数,会++两次++it;}}print_container(v);}void test_vector3(){std::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);print_container(v);// 删除所有的偶数auto it = v.begin();while (it != v.end()){if (*it % 2 == 0){it=v.erase(it);}else{++it;}}print_container(v);}void vector5() {vector<string> v;v.push_back("11111111111111111111");v.push_back("11111111111111111111");v.push_back("11111111111111111111");v.push_back("11111111111111111111");print_container(v);v.push_back("11111111111111111111");print_container(v);}
}

结束语

本期博客就到此结束啦,相信通过自己对vector的实现,大家对vector有了更深的了解。

最后希望友友们给小编点点赞吧,感谢各位友友的支持!!! 

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【H2O2|全栈】关于HTML(1)认识HTML
  • Java通过jna调用c++动态库
  • 基于 AT 固件测试 ESP32 设备作为 WiFi AP 模式创建 TCP Server 开启 UART-to-WiFi 透传模式的指令序列
  • TCP通信实现
  • C++里定义和声明的区别
  • Java数组的定义及遍历
  • 常见分组加密算法的整体结构
  • 第六章 SqlSession 执行 Mapper 过程
  • 学习Power BI第一步先从安装开始(一)
  • springboot系列--自动配置原理
  • QT 联合opencv 易错点
  • 自动驾驶相关的理论基础
  • C语言-数据结构 无向图迪杰斯特拉算法(Dijkstra)邻接矩阵存储
  • vscode 使用git bash,路径分隔符缺少问题
  • 苍穹外卖学习笔记(三)
  • JavaScript类型识别
  • js学习笔记
  • Linux编程学习笔记 | Linux多线程学习[2] - 线程的同步
  • Mysql5.6主从复制
  • 复习Javascript专题(四):js中的深浅拷贝
  • 力扣(LeetCode)965
  • 扫描识别控件Dynamic Web TWAIN v12.2发布,改进SSL证书
  • 我的业余项目总结
  • 译有关态射的一切
  • 在 Chrome DevTools 中调试 JavaScript 入门
  • “十年磨一剑”--有赞的HBase平台实践和应用之路 ...
  • HanLP分词命名实体提取详解
  • ​2021半年盘点,不想你错过的重磅新书
  • ​决定德拉瓦州地区版图的关键历史事件
  • # 日期待t_最值得等的SUV奥迪Q9:空间比MPV还大,或搭4.0T,香
  • #pragma 指令
  • #微信小程序(布局、渲染层基础知识)
  • (~_~)
  • (20050108)又读《平凡的世界》
  • (21)起落架/可伸缩相机支架
  • (70min)字节暑假实习二面(已挂)
  • (ZT)一个美国文科博士的YardLife
  • (阿里云万网)-域名注册购买实名流程
  • (蓝桥杯每日一题)love
  • (面试必看!)锁策略
  • (四)【Jmeter】 JMeter的界面布局与组件概述
  • (一)pytest自动化测试框架之生成测试报告(mac系统)
  • (转)eclipse内存溢出设置 -Xms212m -Xmx804m -XX:PermSize=250M -XX:MaxPermSize=356m
  • (转载)利用webkit抓取动态网页和链接
  • ***检测工具之RKHunter AIDE
  • .[hudsonL@cock.li].mkp勒索加密数据库完美恢复---惜分飞
  • .net core 6 集成和使用 mongodb
  • .net 重复调用webservice_Java RMI 远程调用详解,优劣势说明
  • .net开源工作流引擎ccflow表单数据返回值Pop分组模式和表格模式对比
  • .NET上SQLite的连接
  • .Net下使用 Geb.Video.FFMPEG 操作视频文件
  • .sdf和.msp文件读取
  • ;号自动换行
  • ?.的用法
  • @antv/g6 业务场景:流程图