vector模拟

news/2024/12/5 5:34:11/
先来看看vector的源码,string没有看是因为string严格意义上来讲不属于STL。
源代码之间也是存在区别的,大同小异,可以去网上查如何下载STL的源码库。
先看看<vector>文件中的内容(当做参考即可):
内容是一些头文件和防止重复包含的内容。看内容大概能猜出vector的函数实现在<stl_vector.h>中
<stl_vector.h>中的内容有500+行,一行一行看是没法看的,首先要学会看它的框架,等有一个具体的问题在去看实现的细节。
vector的基本内容:
与string不同,vector的成员是三个迭代器:start,finish,end_of_storage。
这三个迭代器表示的意义从下面的代码找:
由begin函数和end函数可以确定,start是vector数据的起始位置,end是vector数据的结束位置。由capacity函数可以确定end_of_storage是vector空间的结束位置,三者的关系如下图:
在微软编写的vs编译器中,有模拟出size和capacity;
也可以看原始视图:_Myfirst _Mylast _Myend(不同版本的STL源码之前存在区别)
但是看源码现阶段而言存在一些困难,比如下面这种情况
由函数名可知这是一个尾插函数,但是construct(finish, x)表示什么又要找对应的函数,而且construct还不在当前文件中。
但是一定要学会看源码,在工作中看源码已经成为日常了(不管是开始工作时,还是已经工作很长时间了)。
如何看源码是在学习中逐渐掌握的技能。
下面来模拟实现一下vector,顺便说一下,模拟实现vector是为了加深对vector的理解,不是为了写出更好的vector。
vector.h中
#pragma once
#include<iostream>
#include<algorithm>
#include<assert.h>
using namespace std;
namespace my_vector
{
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
vector()
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{}
template<class inputiterator> //模板中可以再添加模板
vector(inputiterator first, inputiterator last)
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{
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);
}
vector(const vector<T>& x) //拷贝构造现代写法
               //vector(const vector& x)没有模板参数也是合法的,虽然很不舒服。写的时候最好把模板参数加上。
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{
vector<T> tmp(x._start, x._finish); //这里的模板参数也可以省略,但不推荐。
swap(tmp);
}
vector(size_t n, const T& val = T())
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
reserve(n); //减少扩容次数
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
vector(int n, const T& val = T()) //添加这个函数的原因在test_vector9()中
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
reserve(n);
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
vector<T>& operator=(vector<T> v) //赋值运算符现代写法
{
swap(v);
return *this;
}
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
}
iterator begin()
{
return _start;
}
const_iterator begin() const
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator end() const
{
return _finish;
}
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _end_of_storage - _start;
}
void push_back(const T& x)
{
if (_finish == _end_of_storage) //空间满了
{
size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newCapacity);
}
*_finish = x;
_finish++;
}
void pop_back()
{
if (_finish - _start)
_finish--;
}
void reserve(size_t n)
{
if (n > capacity())
{
T* tmp = new T[n];
size_t sz = _finish - _start;
//提前储存vector数据的大小,在新空间开辟完成后要重新确立finish的位置
if (_start) //_start可能为nullptr
{
/*memcpy(tmp, _start, size() * sizeof(T));
                                      delete[] _start;*///不使用这部分的代码的原因在test_vector_finally()函数中
for (size_t i = 0; i < size(); i++)
{
tmp[i] = *(_start + i); //tmp是指针,所以能通过下标访问。完成深拷贝。
}
delete[] _start;
}
_start = tmp;
_finish = tmp + sz;
_end_of_storage = tmp + n;
}
}
/*STL库中为:void resize(size_type n, value_type val = value_type());*/
void resize(size_t n, const T& val = T())
//有时候存在编译不通过的情况,换成:void resize(size_t n, T val = T())
               //T不知道是什么类型,所以不传参时,调用T的默认构造函数
               //包括int等内置类型,在c++中也是有默认构造函数和析构函数的,因为要支持模板
{
if (n > capacity())
{
reserve(n);
}
while (size() < n)
{
*_finish = val;
_finish++;
}
if(size() > n)
{
_finish = _start+ n;
}
}
T& operator[](size_t pos)
//下标必须size()下,所以下标只能访问_start,_finish之间的数据,reserve提前开辟的空间不能用下标访问。
{
assert(pos < size());
return *(_start + pos);
}
const T& operator[](size_t pos) const
{
assert(pos < size());
return *(_start + pos);
}
iterator insert(iterator pos, const T& x)
//pos是值传递,pos的改变不会影响it,用引用会报错,比如实参为v.begin(),传入pos的是返回值的中间变量,
                       //具有常属性,iterator无法接收(权限放大),用const_iterator接收pos就不能修改,无法更新位置。
{
assert(pos <= _finish && pos >= _start);
if (size() == capacity())
{
size_t sz = pos - _start;
size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newCapacity);
pos = _start + sz; //如果不写这句会发生一个经典的迭代器失效问题
//reserve后_start,_finish,_end_of_storage都更新了,vector指向一个新的空间,
                              //但是pos没有更新,还是指向原来的位置,这就导致了在移动数据时,end一直大于pos。
                              //迭代器失效大致分为两种,一种是空间改变导致迭代器指向的位置变化,
                              //另一种是迭代器指向的意义发生变化,比如1,2,3,4,5,迭代器指向2,在2之前插入20,变成1,20,3,4,5,此时
                          //pos指向20,此时pos指向的位置变成了20,空间依然有效,但表示的意义已经变了,这也是一种迭代器失效。
                              //vs中会强制检查(断言)出空间改变所导致的迭代器失效,报错。
                              //Linux则相对随便一点,不会检查出来,甚至还可以修改已经失效空间的数据,不报错。
                              //对于意义失效的迭代器,vs和Linux都可以正常访问(读写),意义的失效更多表现为操作逻辑的错误。
}
iterator end = _finish - 1;
//移动数据
while (end >= pos)
{
end[1] = end[0];
end--;
}
*pos = x;
_finish++;
return pos;
}
iterator erase(iterator pos) //一般vector删除数据都不考虑缩容。
                       //缩容方案在:size() < capacity()/2 时可以考虑。开一个size()大小的空间,拷贝数据,释放旧空间。
                       //缩容方案的本质是时间换空间,现在一般考虑时间效率,不考虑空间效率,所以缩容方案很少用。
{
assert(pos >= _start && pos < _finish);
int sz = pos - _start;
while (pos != _finish - 1)
{
*pos = *(pos + 1);
pos++;
}
_finish--;
iterator ret = _start + sz;
return ret; //这个返回值看起来似乎没有意义,实际上,你没法规定erase结束后是否缩容。
                       //如果不缩容,那么返回值当然没有意义,但是如果缩容,返回值就有意义了。
}
void clear()
{
_finish = _start;
}
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
}
test.cpp中
#define _CRT_SECURE_NO_WARNINGS 1
#include"vector.h"
#include<vector>
void test_vector1()
{
my_vector::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.pop_back();
cout << v[3] << endl;
v.resize(10, 5);
v.resize(2);
my_vector::vector<int>::iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
it++;
}
}
void test_vector2()
{
my_vector::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.insert(v.begin(), 0);
my_vector::vector<int>::iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
it++;
}
}
void test_vector3()
{
//要求在所有偶数前插入数字999
        //用于解释为什么insert要返回一个iterator类型
my_vector::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);
my_vector::vector<int>::iterator it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
it = v.insert(it, 999);
//如果insert没有返回值,it就没有办法更新,如果插入数据导致扩容的话,
                       //_start,_finish,_end_of_storage都更新了,it没有办法更新,导致迭代器失效。
it++;
}
it++;
}
it = v.begin();
while (it != v.end())
{
cout << *it << " ";
it++;
}
}
void test_vector4()
{
//my_vector::vector<int> v;
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
cout << v.size() << ":" << v.capacity() << endl;
auto it = find(v.begin(), v.end(), 4);
if (it != v.end())
{
v.erase(it); //删除了4,_finish指向的位置变成了3的下一个位置,也是it此时的位置
}
cout << v.size() << ":" << v.capacity() << endl;
*it = 10; //虽然it已经不指向有效数值,但是仍然在合法空间中(这里使用的my_vector中的erase,没有缩容),不会报错
        //在vs下:
        //如果it是标准库中vector<int>的迭代器,那么这样会报错。vs下会进行强制检查,比如删除3,此时it指向4,
        //这时使用迭代器修改数据,编译器认为迭代器意义改变,迭代器失效,会被检查出来,报错。而且erase没有缩容。
        //相同情况下(迭代器意义改变,空间不改变),insert就没有被检查出来。
        //在Linux下:
        //迭代器失效没有被检查出来。it可以正常的被读写。但是这里的it还是属于迭代器失效的,只是Linux对迭代器失效的反应
        //有点不同,用的时候统一以失效的角度去看待。
cout << *it << endl;
it = v.begin();
while (it != v.end())
{
cout << *it << " ";
it++;
}
}
void test_vector5() //报错代码
{
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
//v.push_back(5);标记1
        //删除偶数
std::vector<int>::iterator it = v.begin();
//string也存在迭代器失效问题,但是在string的学习中没有提到迭代器失效的问题,原因在于string的insert和erase函数
        //主要使用下标来实现函数接口,虽然也有迭代器实现的,但是不常用,所以不怎么关注。
        //迭代器会持续使用,而下标的改变很明显,变化很容易就能看出来。vector的函数接口几乎都是由迭代器实现的。
while (it != v.end())
{
if (*it % 2 == 0)
{
v.erase(it);
}
it++;
}
//上面这部分的代码在vs下必报错,it的意义变了。vs的编译器会检查出来。
        //但是相同的代码再加上标记1的代码,在Linux下就能正常运行起来(没有标记1那行代码,会报段错误(数组越界))
     //所以迭代器的意义变了,放在实际环境中也会发生各种问题,比如第一个数据改为10,加上标记1那行代码,Linux中会得到
        //2,3,5这样的结果。2作为偶数,并没有被删除。
        //段错误的成因:数据:1,2,3,4 第一次找到2,删除,数据变成1,3,4 it指向3,++后指向4,4为偶数,删除,数据变成1,3。
        //it指向_finish,++后指向_finish的下一个位置,无法再满足循环终止条件it != v.end()。it不断++,发生段错误。
        //正确的删除方式如test_vector6
it = v.begin();
while (it != v.end())
{
cout << *it << " ";
it++;
}
}
void test_vector6()
{
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
std::vector<int>::iterator it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
it = v.erase(it);
}
else
{
it++;
}
}
it = v.begin();
while (it != v.end())
{
cout << *it << " ";
it++;
}
}
void test_vector7()
{
my_vector::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
my_vector::vector<int> copy(v.begin(), v.end());
for (size_t i = 0; i < copy.size(); i++)
{
cout << copy[i] << " ";
}
cout << endl;
for (size_t i = 0; i < v.size(); i++)
{
cout << v[i] << " ";
}
}
void test_vector8()
{
my_vector::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
my_vector::vector<int> copy;
copy = v;
for (size_t i = 0; i < copy.size(); i++)
{
cout << copy[i] << " ";
}
cout << endl;
for (size_t i = 0; i < v.size(); i++)
{
cout << v[i] << " ";
}
}
void test_vector9()
{
//使用v(10, 5)初始化调用
        //template<class inputiterator>
        //vector(inputiterator first, inputiterator last)
        // 而不是调用:vector(size_t n, const T& val = T())
        //编译错误指向vector(inputiterator first, inputiterator last)中的push_back(*first);
        //换一组变量测试vector<char> v(10, 'a');结果正常
        //原因在于:不同的实参,表示不同的类型,会匹配到最合适的函数中
        //vector<int> v(10,5)实参类型为int int,vector<char> v(10, 'a')实参类型为int char
        //vector<int> v(10,5)实参类型相同,比起vector(size_t n, const T& val = T())需要一次类型转换,
        //vector(inputiterator first, inputiterator last)更匹配,报错是因为first类型变成int了,没法解引用。
        //标准库中的解决方案是写一个重载函数vector(int n, const T& val = T())
my_vector::vector<int> v(10,5);
for (auto e : v)
{
cout << e << " ";
}
}
void test_vector_finally()
{
class Solution
{
public:
//std::vector<vector<int>> generate(int numRows)
my_vector::vector<vector<int>> generate(int numRows)
{
//std::vector<vector<int>> vv(numRows);std中的vector可以实现杨辉三角的打印
my_vector::vector<vector<int>> vv(numRows);
for (int i = 0; i < numRows; i++)
{
vv[i].resize(i + 1);
vv[i][0] = 1;
vv[i][vv[i].size() - 1] = 1;
}
for (int i = 0; i < numRows; i++)
{
for (size_t j = 0; j < vv[i].size(); j++)
{
if (vv[i][j] == 0)
{
vv[i][j] = vv[i - 1][j - 1] + vv[i - 1][j];
}
}
}
//在类中打印杨辉三角来观察是类里面导致的问题还是类外面导致的问题
for (size_t i = 0; i < vv.size(); i++)
{
for (size_t j = 0; j < vv[i].size(); j++)
{
cout << vv[i][j] << " ";
}
cout << endl;
} //运行后发现是外边的遍历存在问题
return vv;
}
};
int num = 0;
cin >> num;
Solution s;
//std::vector<vector<int>> ret = s.generate(num);
my_vector::vector<vector<int>> ret = s.generate(num); //generate()是传值拷贝,优化为vv直接构造ret
        //其中涉及了一个点叫更深层次的深浅拷贝问题,要是自己找原因会比较困难,直接说结论:函数调用过程
        //vector(const vector<T>& x) -> vector(inputiterator first, inputiterator last) -> void push_back(const T& x) ->
        //void reserve(size_t n)(push_back满时调用)-> memcpy(tmp, _start, size() * sizeof(T));delete[] _start;
//详细过程:在push_back时,前4个vector<int>空间正常push_back,直到空间满了,需要扩容,新空间的值是通过memcpy 完成值的拷贝的,是浅拷贝,对vector这种需要深拷贝的类型就不能合理完成拷贝。delete后数据清除,空间非法。
        //解决方案也很简单,通过一个一个赋值来完成数据的深拷贝
for (size_t i = 0; i < ret.size(); i++)
{
for (size_t j = 0; j < ret[i].size(); j++)
{
cout << ret[i][j] << " ";
}
cout << endl;
}
}
int main()
{
test_vector_finally();
return 0;
}
最后看一下vector<vector<int>>具体样子:

http://www.ppmy.cn/news/87425.html

相关文章

【Java8新特性--->异步处理】CompletableFuture

一、引入 假设一个商品详情页需要以下操作&#xff1a; 查询展示商品的基本信息耗时&#xff1a;0.5s 查询展示商品的销售信息耗时&#xff1a;0.7s 查询展示商品的图片信息耗时&#xff1a;1s 查询展示商品销售属性耗时&#xff1a;0.3s 查询展示商品规格属性耗时&#xff1a…

实验11 人工神经网络(2)

1. 实验目的 ①掌握梯度下降法的优化算法&#xff1b; ②能够使用tf.keras构建Sequential模型&#xff0c;完成多分类任务。 2. 实验内容 ①下载MNIST数据集&#xff0c;建立神经网络模型&#xff0c;实现对MNIST手写数字数据集的识别&#xff0c;调整超参数和训练参数&…

一则历史:为什么网络路径前加一个盘符还能正常工作

有一个比较知名的奇异特性&#xff1a;文件系统在解析 UNC(Universal Naming Convention) 路径时&#xff0c;会故意忽略掉最前面添加的盘符字母。 举个例子&#xff0c;假设服务器上有一个共享文件夹&#xff0c;其路径为&#xff1a;\\server\share\directory&#xff0c;如果…

android 12.0 设置app为默认浏览器

1.概述 在12.0的产品定制化中,如果系统安装多个浏览器时,需要设置默认浏览器来完成需求,这就需要看系统设置中的相关源码 当出现多个浏览器时,该如何设置默认浏览器呢, 其实在Settings 默认应用->浏览器应用 当点击选择浏览器时会调用 /package/app/PermissionControll…

基于html+css的图片展示93

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

node.js+vue房屋租赁管理系统z0g8w

本系统主要包括以下功能模块&#xff1a;租户、出租人、房源信息、预约看房、合同信息等模块。 其中设计的主要功能如下&#xff1a; &#xff08;1&#xff09;用户的注册和登录本系统&#xff0c;登录到系统的首页。 &#xff08;2&#xff09;用户可以发布自己的房源信息…

C++的指针和引用

文章目录 C的指针和引用C指针C中内存单元内容和地址指针的定义和间接访问操作指针和数组左值和右值 几种C中的原始指针原始指针的基本运算 存储区域划分栈和队列代码在内存单元中的分布cpp动态分配资源和回收原则资源管理方案-RAIIC中几种变量对比 内存泄漏智能指针C的智能指针…

【Nginx】- 02 Nginx反向代理、负载均衡、动静分离、虚拟域名配置

Nginx反向代理、负载均衡、动静分离、虚拟域名配置 1 反向代理1.1 nginx配置 2 负载均衡2.1 ngxin配置2.2 nginx负载均衡策略2.2.1 默认轮询2.2.2 权重2.2.3 iphash2.2.4 least_conn 3 动静分离3.1 打包前端项目3.2 部署前端项目3.3 配置静态图片资源访问 4 虚拟域名配置4.1 修…