【爱上C++】vector模拟实现
文章目录
- 前言
- 一:基本框架
- 1.结构的定义
- 2.构造函数
- ①.详解 const T& val = T()
- ②.为什么要多加一个int类的带参构造?】
- 3.析构函数
- 4.size()和capacity()
- 5.push_back尾插
- 6.operator[]
- operator[]的返回类型为T&有以下几个原因:
- 二:迭代器的实现
- 1.begin()和end()
- 2.迭代器区间构造
- 三:reserve 引发的相关问题
- 1.内部迭代器失效
- 2.外部迭代器失效
- 3.正确的实现
- _start 指针的作用
- _start[i] 的含义
- 4.memcpy带来的问题
- 四:insert 引发的相关问题
- 1.内部迭代器失效
- 2.外部迭代器失效
- 五:erase 引发的相关问题
- 修改后的执行过程
- 六:其他操作
- resize
- 赋值运算符重载
- 现代写法的思路
- 为什么现代写法传参数时不能传引用?
- 现代写法传参为什么不能传引用的具体原因:
- 七:完整代码
前言
上一节我们讲了vector的基本使用,现在我们讲解vector的模拟实现,其中有三大重难点:
1.vector是如何进行设计与封装的
2.迭代器失效问题
3.memcpy,memmove导致的浅拷贝问题一:基本框架
1.结构的定义
#include #include using namespace std; namespace myvector { template class vector { public: // Vector的迭代器是一个原生指针 typedef T* iterator; typedef const T* const_iterator; private: iterator _start; // 开始位置 iterator _finish; // 结束位置 iterator _endofstorage; // end of storage }; }
在 vector 类中,我们通常会使用_指针_来表示迭代器,因为指针天然支持指针算术运算和解引用操作,可以方便地遍历和访问元素。使用 typedef 定义迭代器类型可以使代码更加灵活和可维护。
使用 iterator 类型有以下几个原因:
- 可读性和维护性: 使用 iterator 使得代码更具可读性。例如,当看到 iterator 类型时,很容易理解这是一个指向容器元素的指针,而不是其他类型的指针。
- 灵活性: 如果将来需要更改迭代器的实现方式,只需要修改 typedef 定义,而不需要修改所有使用迭代器的代码。例如,如果将来决定使用自定义的迭代器类,而不是原始指针,只需要修改 typedef 语句。
- 与 STL 接口一致: 这使得 vector 类的接口与标准模板库(STL)容器的接口一致,便于用户使用和理解。例如,STL 容器如 std::vector 也使用迭代器来遍历和操作元素。
_start:
- 这是一个指针,指向分配的内存空间中的第一个元素。
- 在 vector 类中,_start 指向当前存储的第一个元素的位置。
_finish:
- 这是一个指针,指向当前存储的最后一个元素之后的位置。
- 在 vector 类中,_finish 指向最后一个元素的下一个位置。即,有效元素的范围是从 _start 到 _finish-1。
_endofstorage:
- 这是一个指针,指向分配的内存空间的末尾位置。
- 在 vector 类中,_endofstorage 指向当前分配的内存空间的末尾,即容器可以存储元素的最大位置。
所以由图我们可以知道:
_size=_finish-_start
_capacity=_endofstorage-_start2.构造函数
构造函数有三类:无参构造函数,带参构造,迭代器区间构造放在 二:迭代器的实现 中讲述。
//无参默认构造 vector() :_start(nullptr) , _finish(nullptr) , _endofstorage(nullptr) { } // 带参构造函数,创建包含 n 个 val 值的 vector vector(size_t n, const T& val = T()) { resize(n, val); } //带参构造(int) vector(int n, const T& val = T()) { resize(n, val); }
①.详解 const T& val = T()
关于 const T& value = T() 作为默认参数的详细解释,我们需要理解以下几个概念:默认参数、匿名对象、以及如何结合使用它们。
默认参数
默认参数允许函数在调用时省略某些参数。编译器会使用提供的默认值来填补这些被省略的参数。对于构造函数来说,默认参数提供了一种灵活的初始化方式。
匿名对象
匿名对象是指没有绑定到任何变量的临时对象。在 C++ 中,可以通过直接调用构造函数来创建匿名对象。例如,T() 就是一个创建类型为 T 的默认构造对象的表达式。
const T& value = T()
现在,让我们将这些概念结合起来看 const T& value = T() 是如何工作的。- 默认参数的使用
在构造函数定义中:
vector(int n, const T& value = T())
value 是一个默认参数,其默认值是一个匿名对象 T()。 - 匿名对象的创建
T() 表达式创建了一个类型为 T 的匿名对象。对于基础类型 int,T() 等价于 0。对于用户定义的类型 T,它调用 T 的默认构造函数创建一个默认初始化的对象。 - 引用绑定
const T& value = T() 意味着默认参数 value 是一个对匿名对象的常量引用。这里引用了临时对象,但因为是常量引用,所以该临时对象在整个函数调用过程中是有效的(临时对象的生命周期延长至引用的生命周期结束)。
②.为什么要多加一个int类的带参构造?】
思考一下,如果不加vector(int n, const T& val = T()),下面这个代码是否有问题?
vector v1(5, "1111"); for (auto e : v1) { cout cout if (_start) { delete[] _start; _start = _finish = _endofstorage = nullptr; } } return _finish - _start; } size_t capacity() const { return _endofstorage - _start; } if (_finish == _endofstorage)//如果满了则要扩容 { size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2; //如果 capacity() == 0,则让 capacity()=4, //如果 capacity() !=0 ,则让 capacity()=capacity()*2 reserve(newcapacity); } *_finish = x; ++_finish; } void pop_back() { assert(size()0); --_finish; } assert(pos
- 默认参数的使用