今天博主继续带来STL源码剖析专栏的第二篇博客了!
今天带来vector的模拟实现!
其实在很多人学习C++过程中,都是只学习一些STL的使用方式,并不了解底层的实现。博主本人认为,这样的学习这样的技术是不深的。如果我们想要熟悉的掌握一门语言,我认为,底层的实现必不能少!
但是,想从0开始模拟实现STL的容器,需要我们熟悉C++的语法,特别是类和对象部分的知识!
博主学习C++到现在,我认为C++类和对象基本语法的学习比任何部分都要重要,而我花在这上面的时间也是最多的!只要搞清楚C++面向对象编程的基本规则,我们才能在STL的世界里游刃有余!
所以我希望大家在学习STL之前,先将数据结构与算法,C++的类和对象部分内容掌握熟悉!


前言

那么这里博主先安利一下一些干货满满的专栏啦!

手撕数据结构https://blog.csdn.net/yu_cblog/category_11490888.html?spm=1001.2014.3001.5482这里包含了博主很多的数据结构学习上的总结,每一篇都是超级用心编写的,有兴趣的伙伴们都支持一下吧!
算法专栏https://blog.csdn.net/yu_cblog/category_11464817.html这里是STL源码剖析专栏,这个专栏将会持续更新STL各种容器的模拟实现。

STL源码剖析https://blog.csdn.net/yu_cblog/category_11983210.html?spm=1001.2014.3001.5482


实现过程中要注意的一些点

vector类其实是我们非常常用的一个容器,其底层其实就是一个顺序表。

博主在带大家实现的过程中,并不会把STL里面每一个成员函数都去模拟实现一次,我们模拟实现容器底层的目的其实是去学习里面实现的思路。

因此,博主只会将一些重要、常用的成员函数模拟实现给大家。

另外,除了博主实现的源代码之外,博主还提供一份测试代码,它可以帮助我们检查自己写的代码是否准确。

在测试代码中遇到的问题和解决思路以及一些细节的处理,博主都在.h和.cpp代码里用注释的形式给大家说明白(注释满满干货不要错过噢)。

希望大家可以从中学到知识。

MyVector.h完整源代码

#pragma once
#include<cassert>
#include<string.h>
#include<algorithm>
#include<functional>
namespace yufc {
template<class T>
class vector {
public:
typedef T* iterator;//这个要放成共有,不然迭代器外面访问不了 -- 定义迭代器类型是指针类型
typedef const T* const_iterator;
//vector的迭代器就是原生指针
iterator begin() {
return _start;
}
iterator end() {
return _finish;
}
//如果不提供const迭代器 -- 如果传了const的vector是遍历不了的,因为权限不能放大
const_iterator begin()const { //不能返回普通迭代器,要返回const迭代器
return _start;
}
const_iterator end()const {
return _finish;
}
vector() :_start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {}
~vector() {
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
//stl的vector还支持使用迭代器区间去构造
//为什么需要一个其它类型的迭代器?
//因为他要支持用其它容器的迭代器的区间构造
template<class InputIterator>
vector(InputIterator first, InputIterator last)
:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{
while (first != last) {
//不能直接push_back(),直接push_back()会崩溃 -- 因为没有初始化 -- 野指针
push_back(*first);
++first;
}
}
vector(size_t n, const T& val = T())
:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{
//用n个值去构造
reserve(n);
for (size_t i = 0; i < n; i++) {
push_back(val);
}
}
//传统拷贝构造
#if 0
vector(const vector<T>& v) {
_start = new T[v.size()];//这里是给size还是capacity呢?STL没有要求
//memcpy(_start, v._start, sizeof(T) * v.size());
//解决vector<vector<int>>深拷贝的情况,memcpy不能帮助内层的自定义类型完成深拷贝,赋值可以(我们写好了自定义类型的赋值)
for (size_t i = 0; i < v.size(); i++) {
_start[i] = v._start[i];
}
_finish = _start + v.size();
_end_of_storage = _start + v.size();//因为T[]里面给了size(),所以这里也应该是v.size()
}
#endif
//写法2
#if 0
vector(const vector<T>& v)
:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{
reserve(v.size());
for (const auto& e : v) { //注意:这里拷贝过去一定要引用,不然又要拷贝了
//写深拷贝自己还要调深拷贝 -- 肯定是会出问题的
push_back(e);
}
//不需要去调整_finish,因为push_back()已经搞定了
}
#endif //现代写法拷贝构造 -- 复用迭代器区间构造函数
void swap(vector<T>& v) {
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
#if 1
vector(const vector<T>& v)
:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{
vector<T>tmp(v.begin(), v.end());
swap(tmp);//当然自定义类型自己写的swap更高效 -- 我们自己实现一个
}
#endif
vector<T>& operator=(vector<T> v) { //v1=v2
//v是v2的拷贝,而且是局部的,swap完之后给到v1,v还会自动析构(因为是局部对象)
swap(v);
return *this;
}
size_t capacity()const {
return _end_of_storage - _start;
}
size_t size()const {
return _finish - _start;
}
void reserve(size_t n) {
if (n > capacity()) {
T* tmp = new T[n];
size_t sz = size();
if (_start) {
//memcpy(tmp, _start, sizeof(T) * sz);//拷贝size()个字节过去,先放到tmp里面
//同样,解决
//vector<自定义类型>的问题
for (size_t i = 0; i < sz; i++) {
tmp[i] = _start[i];//当T是自定义类型时,调用T类型的operator=
}
delete[] _start;
}
_start = tmp;
//_finish = _start + size();//这里不要现场去算size()
//因为size()是用start减出来的,上面那一行start已经变了
//所以在前面我们最好保留一下size先
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
void resize(size_t n, const T& val = T()) {
//1.扩容+初始化
//2.初始化
//3.删除数据
if (n > capacity()) {
reserve(n);
}
if (n > size()) {
//初始化填值
while (_finish < _start+n) {
*_finish = val;
++_finish;
}
}
else {
_finish = _start + n;
}
}
T& operator[](size_t pos) {
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos) const {
assert(pos < size());
return _start[pos];
}
void push_back(const T& x) {
//加了const保证传什么类型都行,因为隐式类型转换临时变量具有常性
if (_finish == _end_of_storage) {
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = x;
++_finish;
}
void pop_back() {
assert(_finish > _start);//为空是不能删的
--_finish;
}
void insert(iterator pos, const T& x) {
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _end_of_storage) {//扩容
//记住pos和start的相对位置
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len;//更新pos的位置 -- 解决迭代器失效
}
//挪动数据
iterator end = _finish - 1;
while (end >= pos) { // insert要扩容的时候,这个循环就失效了,停不下来了,为什么?
*(end + 1) = *end;
end--;
}
//扩容之后,旧空间的数据拷贝到新空间
//旧空间已经被释放了 -- pos是指向旧空间的一个数字的位置的
//pos成了野指针! -- 迭代器失效
//所以我们要把pos更新一下
// -- 修改了内部的pos之后,其实问题还没有根本的解决
*pos = x;
++_finish;
}
iterator erase(iterator pos) {
assert(pos >= _start);
assert(pos < _finish);
//挪动覆盖删除
iterator begin = pos + 1;
while (begin < _finish) {
*(begin - 1) = *begin;
++begin;
}
--_finish;
//删除了位置的下一个位置 -- 还是pos
return pos;
}
T& front() {
assert(size() > 0);
return *_start;
}
T& back() {
assert(size() > 0);
return *(_finish - 1);
}
private:
iterator _start;//start相当于整个数组的开始位置
iterator _finish;//[_start,_finish)
iterator _end_of_storage;
};
}

test.cpp测试源文件完整代码

#define _CRT_SECURE_NO_WARNINGS 1

#include"MyVector.h"
#include<iostream>
#include<vector>
using namespace std; void PrintVector(yufc::vector<int>& v) {//在没有写拷贝构造的时候,这里一定要引用的,不然浅拷贝,释放两次会出问题
yufc::vector<int>::iterator it = v.begin();
while (it != v.end()) {
cout << *it << " ";
it++;
}
cout << endl;
}
void PrintVector(const yufc::vector<int>& v) {
yufc::vector<int>::const_iterator it = v.begin();
while (it != v.end()) {
cout << *it << " ";
it++;
}
cout << endl;
}
void test_vector1() {
yufc::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);
for (int i = 0; i < v.size(); i++) {
++v[i];
cout << v[i] << " ";
}
cout << endl;
}
void test_vector2() {
yufc::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);
yufc::vector<int>::iterator it = v.begin();
while (it != v.end()) {
(*it)--;
cout << *it << " ";
it++;
}
cout << endl; for (auto& e : v) {
e++;
cout << e << " ";
}
cout << endl; it = v.begin();
while (it != v.end()) {
cout << *it << " ";
it++;
}
cout << endl; PrintVector(v);
}
void test_vector3() {
const yufc::vector<int>v;
#if 0 //改不了的
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
#endif
PrintVector(v);
//范围for虽然是傻瓜式的替换 -- 但是它也是会调用const迭代器的
}
//迭代器失效问题
void test_vector4() {
yufc::vector<int>v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
//v.push_back(5); // 我们发现 -- 原来4个数据,insert之后要扩容的时候,就出现问题了!
//v.push_back(6);
yufc::vector<int>::iterator it = v.begin();
while (it != v.end()) {
cout << *it << " ";
it++;
}
cout << endl;
//
auto p = find(v.begin(), v.end(), 3);
if (p != v.end()) {
v.insert(p, 30); //在内部初步解决迭代器失效之后,其实问题还是没有根本解决!
//因为内部的pos更新不会影响p
//所以我们在使用的时候
//在p位置插入数据以后,不要访问p,因为p可能失效了
//因为我们使用STL的时候,不了解底层的扩容机制,所以以后我们insert之后,不要去访问p位置! -- 可能会有问题 //那能否把insert第一个参数改成&行吗? -- 尽量不要这样做 -- 和库保持一致好
//虽然我们看似解决了问题 -- 但是改了可能会引出其它问题 -- 比如,头插,我们想传v.begin();v.insert(v.begin(), 0);
//编不过,因为类型不匹配
#if 0
cout << *p << endl;
v.insert(p, 40);
#endif
}
PrintVector(v);
}
//erase迭代器会失效吗?
//库里面的erase会失效吗? -- 不知道
//STL并没有规定什么机制
//有没有一种可能,当size()<capacity()/2的时候,缩一下容(缩容:以时间换空间)
//反正用完那个别访问,别动那个p就行了!
void test_vector5() {
yufc::vector<int>v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
auto p = find(v.begin(), v.end(), 3);
if (p != v.end()) {
v.erase(p);
}
PrintVector(v);
}
void test_vector6() {
yufc::vector<int>v;
#if 0
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
#endif
//情况1和2
//我们发现此时有这个5 -- 程序正常,没有 -- 崩溃
#if 1
//情况3
v.push_back(1);
v.push_back(2);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
#endif
//要求删除所有的偶数
auto it = v.begin();
//情况3的时候,it会跳过end(),直接继续往后了 -- 崩溃
//所以下面这段算法是不正确的!
//其实我们看源代码可以发现 -- erase是有返回值的 -- 会更新一下迭代器
//STL规定erase返回删除位置下一个位置的迭代器!
//vs这个编译器是查的很严格的 -- erase之后不允许去访问! -- 访问就报错
//Linux下就不同
while (it != v.end()) {
if (*it % 2 == 0) {
it = v.erase(it);//我们要返回一个it才行
}
else {
++it;
}
}
PrintVector(v);
}
//总结
//其实迭代器失效,我们在自己做OJ的时候也能体会的,插入或者删除之后,迭代器指向了它不该指向的地方
//在使用vector的迭代器的时候,要多调试! void test_vector7() {
//系统默认的拷贝
//1.自定义类型 -- 调拷贝构造 -- 默认生成的 -- 浅拷贝
//2.内置类型 -- 值拷贝 -- 浅拷贝
yufc::vector<int>arr;
arr.push_back(1);
arr.push_back(2);
arr.push_back(3);
for (auto i : arr) {
cout << i << " ";
}
cout << endl;
yufc::vector<int>arr2 = arr;
//调用系统默认的话就是浅拷贝,这样程序肯定会崩溃的 -- 析构了两次
//而且如果是浅拷贝,改一边的值另一边也会被改变
arr2[0] = 100; //两边都会被改变的 -- 所以我们需要深拷贝!
for (auto i : arr) {
cout << i << " ";
}
cout << endl;
for (auto i : arr2) {
cout << i << " ";
}
}
void test_vector8() {
string s("hell");
yufc::vector<int>vs(s.begin(), s.end());
for (auto i : vs) {
cout << i << " ";
}
cout << endl;
//赋值
yufc::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(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
vs = v;//要实现一个深拷贝
//当然,如果f11 -- 这里是先进去拷贝的,因为传参要拷贝
//走完拷贝就进去赋值了
for (auto i : vs) {
cout << i << " ";
}
}
//其实内置类型也有构造
void test_type() {
int i = 0;
int j = int();
int k = int(10);
}
void test_vector9() {
//vector<int>v = { 1,2,3,4,5 };
vector<int>v(10);
yufc::vector<int>v(10, 1);//这样就报错了,如果传了两个参数都是int
//这里出问题就是匹配问题,它匹配到不该去的地方去了
//为什么 -- 因为迭代器区间构造那个函数,更适合这样传参 -- 所以进到那里去了
//解决:
//1.传参的时候写(10u,1)表示这个是个usigned int类型
//2.把vector(size_t n,const T&val=T())里面的size_t改成int,也可以解决 -- 但是stl官方给的是size_t
//3.复制一份,改成int,弄个重载就行 -- stl_3.0是这样解决的
for (auto i : v) {
cout << i << " ";
}
cout << endl;
}
void test_vector10() {
yufc::vector<int>v;
v.resize(10, 1);
for (auto e : v) {
cout << e << " ";
}
cout << endl; yufc::vector<int>v1;
v1.reserve(10);
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.resize(8, 8);
for (auto e : v1) {
cout << e << " ";
}
cout << endl; v1.resize(20, 20);
for (auto e : v1) {
cout << e << " ";
}
cout << endl; v1.resize(3);
for (auto e : v1) {
cout << e << " ";
}
cout << endl;
} //vector到目前为止还有最后一个坑还没有解决
//我们用自己的vector测试一下杨辉三角
class Solution {
public:
yufc::vector<yufc::vector<int>> generate(int numRows) {
yufc::vector<yufc::vector<int>> ret(numRows);
for (int i = 0; i < numRows; ++i) {
ret[i].resize(i + 1);
ret[i][0] = ret[i][i] = 1;
for (int j = 1; j < i; ++j) {
ret[i][j] = ret[i - 1][j] + ret[i - 1][j - 1];
}
}
return ret;
}
};
void test_vector11() {
Solution().generate(10);
//报错 -- 在函数结束的时候崩溃了
//修改一下 -- 把函数改成void类型,不return -- 没事了
//为什么?
//拷贝有问题了 -- 普通的vector深拷贝没问题 -- 这里的深拷贝有问题
//里面是浅拷贝,外面是深拷贝
// // //即:我们的vector是深拷贝了 -- 但是里面的自定义类型没有深拷贝!!!!!!!!!!
//我们拷贝的时候,外面的空间是深拷贝,但是里面的东西,还是指向以前的地方
//传统写法是因为memcpy导致的 -- 我们将memcpy改成一个一个赋值就行了(这样的话自定义类型也可以完成深拷贝(调用自己的deepcopy))
//现代写法是因为因为要调用push_back(),而push_back()里面调用reserve(),是reserve()里面的memcpy出问题
}
int main() {
//test_vector1();
//test_vector2();
//test_vector3();
//test_vector4();
//test_vector5();
//test_vector6();
//test_vector7();
//test_vector8();
//test_type();
//test_vector9();
//test_vector10();
test_vector11();
return 0;
} //我们一定要清晰一个点:
//STL只是一个规范
//这个规范怎么去实现,是没有规定的!
//VS-PJ版 g++ SGI版

尾声

看到这里,相信大家对vector类的模拟实现已经有一定的了解了!vector的模拟实现,是我们掌握STL的开始,后面,博主将会给大家带来list,stack等等STL容器的模拟实现,持续关注,订阅专栏,点赞收藏都是我创作的最大动力。

转载时请注明作者和出处。未经许可,请勿用于商业用途 )
更多文章请访问我的主页

@背包https://blog.csdn.net/Yu_Cblog?spm=1000.2115.3001.5343

【STL源码剖析】vector类模拟实现 了解底层-走进底层-掌握底层【超详细的注释和解释】的更多相关文章

  1. STL源码剖析-vector

    STL(Standard Template Library) C++标准模板库,acm选手一定对它不陌生,除了算法,那几乎是“吃饭的家伙了”.我们使用库函数非常方便,且非常高效(相对于自己实现来说). ...

  2. STL源码剖析读书笔记之vector

    STL源码剖析读书笔记之vector 1.vector概述 vector是一种序列式容器,我的理解是vector就像数组.但是数组有一个很大的问题就是当我们分配 一个一定大小的数组的时候,起初也许我们 ...

  3. STL"源码"剖析-重点知识总结

    STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...

  4. 【转载】STL"源码"剖析-重点知识总结

    原文:STL"源码"剖析-重点知识总结 STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点 ...

  5. STL"源码"剖析

    STL"源码"剖析-重点知识总结   STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略 ...

  6. STL源码剖析之空间配置器

    本文大致对STL中的空间配置器进行一个简单的讲解,由于只是一篇博客类型的文章,无法将源码表现到面面俱到,所以真正感兴趣的码农们可以从源码中或者<STL源码剖析>仔细了解一下. 1,为什么S ...

  7. STL源码剖析之组件

    本篇文章开始,进行STL源码剖析的一些知识点,后续系列笔记全是参照<STL源码剖析>进行学习记录的 STL在现在的大部分项目中,实用性已经没有Boost库好了,毕竟STL中仅仅提供了一些容 ...

  8. 【STL 源码剖析】浅谈 STL 迭代器与 traits 编程技法

    大家好,我是小贺. 点赞再看,养成习惯 文章每周持续更新,可以微信搜索「herongwei」第一时间阅读和催更,本文 GitHub : https://github.com/rongweihe/Mor ...

  9. (原创滴~)STL源码剖析读书总结1——GP和内存管理

    读完侯捷先生的<STL源码剖析>,感觉真如他本人所说的"庖丁解牛,恢恢乎游刃有余",STL底层的实现一览无余,给人一种自己的C++水平又提升了一个level的幻觉,呵呵 ...

  10. 《STL源码剖析》环境配置

    首先,去侯捷网站下载相关文档:http://jjhou.boolan.com/jjwbooks-tass.htm. 这本书采用的是Cygnus C++ 2.91 for windows.下载地址:ht ...

随机推荐

  1. springboot 参数通过 @RequestBody传递的写法

    controller中有时候参数是使用 @RequestBody传递的这种怎么写? 直接上代码 @RequestMapping("/test") public void getAc ...

  2. 7 Englishi 词根

    1.跟直播,跟复习课,完成作业 2. 基础差加餐,听录播 3.如何听课 4.单词学习反复多次 如何记笔记 语块的汉语意思 单词的记忆方式和固定表达 俩种方式记忆单词 语块关联记忆 基础词根词缀(6节课 ...

  3. java项目实战-mybatis-基本配置01-day22

    目录 0. mysql navicate链接分享 1. mvn坐标引入 2. mysql的核心配置文件 3. 返回值类型 别名 4. 将数据的配置提取配置文件 4. log4j修改日志输出 0. my ...

  4. java基础-反射-day15

    目录 1. 案例引入 2. Class 的理解 3. 反射的详细使用 4. Class 类都有哪些实例 5. 详细使用 6. 获取属性 7. 获取方法 8 获取类的 接口 注解 所在的包 9. 思考 ...

  5. Redis 集群模式搭建

    本文为博主原创,未经允许不得转载: 目录: 1. 哨兵模式与集群模式对比 2. Redis 集群架构搭建 3. 集群原理分析 4. 集群元数据维护方式对比 5. redis 分布式寻址 6. 集群选举 ...

  6. WPF|如何在 WPF 中设计漂亮的社交媒体信息仪表板

    1. 效果展示 先来直接欣赏效果: 2. 准备 创建一个WPF工程,比如站长使用 .NET 7 创建名为 Dashboard3 的WPF项目,添加一些图片资源,项目目录如下: 2.1 图片资源 可在网 ...

  7. 数字IC设计全流程介绍

    数字IC设计全流程设计 掌握数字集成电路设计的流程 数字设计流程中每个阶段主要做哪些工作? 数字设计流程中每个阶段使用的主要EDA工具? 数字电路常用软件公司Mentor(questasim),Syn ...

  8. Qt5.9 UI设计(四)——布局设计及自定义界面

    前言 前面我们已经创建了mainwindow ControlTabWidget ControlTreeWidget maintitlebar 4个UI几面,我们需要将其他三个UI放置到mainwind ...

  9. ZHS16GBK字符集下面Oracle数据库varchar与nvarchar的验证

    ZHS16GBK字符集下面Oracle数据库varchar与nvarchar的验证 背景 周末分析了 SQLServer mysql等数据库 想着继续分析一下oracle数据库 这边oracle使用的 ...

  10. Kubeadm 安装支持IPV6 K8S1.28.x的简单过程

    Kubeadm 安装支持IPV6 K8S的简单过程 背景 手贱 找了一个晚上想尝试安装一个K8S集群 并且可以支持IPV6 协议栈的 然后就开始各种百度. 各种处理 找到了一堆歪门邪道. 但是还不知道 ...