1.为什么要模拟实现vector?
模拟实现vector是为了深入理解和学习C++标准库中vector容器的工作原理和实现细节。
vector是C++标准库中最常用的容器之一,它提供了动态数组的功能,并且具有自动扩容和内存管理的特性,使得在使用时非常方便。
模拟实现vector有以下几个优点:
- 学习数据结构与算法:实现vector需要涉及到动态数组的增删查改等操作,这可以让开发者学习到关于数据结构和算法的相关知识。
- 理解内存管理:vector需要管理动态内存分配和释放,了解如何使用指针、动态分配内存以及防止内存泄漏等问题是很重要的。
- 理解模板编程:vector是一个通用的容器,可以存储不同类型的元素。模拟实现vector需要涉及到模板编程,让开发者更好地理解C++中的模板机制。
- 提高编程技巧:通过实现vector,开发者可以锻炼自己的编程技巧和代码设计能力,同时也可以发现一些潜在的问题和改进的空间。
虽然C++标准库中已经提供了vector容器,但是模拟实现vector对于学习和理解C++的底层实现以及算法和数据结构有很大的帮助。实际开发中,我们通常使用标准库中的vector,因为它已经经过了充分的测试和优化,具有更好的性能和稳定性。然而,通过模拟实现vector,我们可以更好地理解背后的原理和设计思想。
2.模拟实现vector需要注意哪些问题?
在模拟实现vector时,需要注意以下几个关键问题:
- 动态内存管理:vector是动态数组,需要在运行时动态分配内存。要确保在插入、删除元素时正确地分配和释放内存,避免内存泄漏和悬挂指针的问题。
- 自动扩容:vector具有自动扩容的功能,当容器中的元素数量超过当前容量时,需要重新分配更大的内存空间,并将原有的元素拷贝到新的内存空间中。
- 模板编程:vector是一个通用的容器,可以存储不同类型的元素。因此,在模拟实现vector时需要使用模板编程,确保容器可以适用于不同类型的数据。
- 迭代器:vector应该提供迭代器用于访问容器中的元素。迭代器应该支持指针的各种操作,如指针算术、解引用等。
- 成员函数和接口:模拟实现的vector应该提供类似于标准库中vector的成员函数和接口,例如push_back、pop_back、size、capacity等等。
- 异常安全性:在模拟实现vector的过程中,要确保对异常的处理,避免因为异常而导致内存泄漏或数据结构被破坏。
- 性能优化:模拟实现的vector可能不如标准库中的vector性能优越,但是仍然要考虑一些性能优化的方法,例如避免频繁的内存分配和释放、减少不必要的拷贝等。
- 测试:在实现过程中,要进行充分的测试,确保vector的各种功能和接口都能正确地工作,并且能够处理各种边界情况。
总的来说,模拟实现vector需要考虑到数据结构、内存管理、模板编程、异常处理以及性能优化等方面的问题,确保实现的容器能够稳定、高效地工作。模拟实现vector是一个很好的学习和锻炼编程能力的练习,同时也能更深入地理解C++标准库中容器的原理和设计思想。
3.vector模拟实现
3.1 命名空间vector的成员变量定义
要定义成员变量,首先我们要了解vector的成员变量有哪些,它和我们在数据结构中的顺序表虽然很像,但又有很多不同之处,在vector中三个很重要的成员变量分别是:
iterator _start
: 这是指向第一个元素的迭代器,它表示容器中的有效元素的起始位置。iterator _finish
: 这是指向最后一个元素的下一个位置的迭代器,也就是超出有效范围的位置。它用于表示容器中已经存储的元素的末尾位置。iterator _end_of_storage
: 这是指向分配的内存空间的末尾位置的迭代器,即容器实际分配的内存空间的结束位置。这个位置可能会在容器扩容时变化。
这样的定义是为了将容器的内存管理和元素访问分离开来,通过这三个迭代器,vector可以有效地管理容器的内存空间和元素的访问。
当vector中的元素数量超过当前内存空间的容量时,会进行扩容操作。此时,会重新分配更大的内存空间,并将已有的元素从旧内存复制到新内存,然后更新 _start、_finish 和 _end_of_storage 迭代器的值,使其正确地指向新的内存空间。
定义这三个迭代器的方式,使得vector能够在不同的操作下保持内存管理和元素访问的一致性。它们将内存的分配和元素的访问解耦,使得vector的实现更加灵活和高效。
所以第一步我们需要先定义迭代器和对应的迭代器成员
namespace xzq//为了和原vector做区分,放在不同名的命名空间内
{
template<class T>//模板命名
class vector
{
public:
typedef T* iterator; //变量模板迭代器指针
typedef const T* const_iterator; //常量模板迭代器指针
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
}
3.2 迭代器成员函数begin()和end()定义
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
iterator begin()
:返回一个指向容器中第一个元素的迭代器(非 const 版本)。
iterator end()
:返回一个指向容器中最后一个元素的后一个位置的迭代器(非 const 版本)。
const_iterator begin() const
:返回一个指向容器中第一个元素的迭代器(const 版本,用于常量对象)。
const_iterator end() const
:返回一个指向容器中最后一个元素的后一个位置的迭代器(const 版本,用于常量对象)。
这些函数允许你通过迭代器遍历访问 vector 中的元素。区别在于,非 const 版本的函数返回的迭代器可以用于修改元素,而 const 版本的函数返回的迭代器只能用于访问元素而不能修改。
3.3 构造函数、拷贝构造函数和析构函数
3.3.1 构造函数
vector()
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{}
这是一个默认构造函数的实现,用于初始化一个空的 vector 对象。在这个构造函数中,通过初始化成员变量 _start、_finish 和 _end_of_storage 为 nullptr,表示该 vector 对象没有分配任何内存空间,也没有存储任何元素。
这个构造函数在创建一个空的 vector 对象时使用,因为刚开始还没有元素需要存储,所以内存空间的指针都被设置为 nullptr。随后在 vector 的操作中,当需要添加元素时,会根据需要进行内存空间的分配,并逐步调整这些指针的值。
总之,这个构造函数的目的是创建一个空的 vector 对象,并初始化内部的迭代器成员,为后续的操作做好准备。
3.3.2 拷贝构造函数
vector(const vector<T>& v)
三种写法
第一种写法:直接分配内存并逐个赋值
vector(const vector<T>& v)
{
_start = new T[v.size()]; // v.capacity()也可以
//memcpy(_start, v._start, sizeof(T)*v.size());
for (size_t i = 0; i < v.size(); ++i)
{
_start[i] = v._start[i];
}
_finish = _start + v.size();
_end_of_storage = _start + v.size();
}
这里建议是不要用memcpy函数,memcpy 不会调用元素类型的拷贝构造函数,可能会导致未初始化或释放的内存问题。它仅适用于 POD(Plain Old Data)类型,对于非 POD 类型(如含有指针、自定义构造函数等的类型),使用 memcpy 可能会导致错误。
优点:
直观:清晰地展示了如何手动分配内存、逐个赋值元素。
缺点:
重复操作:需要逐个赋值元素,可能会有一些不必要的重复操作。
内存泄漏:如果在分配内存和赋值之间出现异常,可能导致内存泄漏。
第二种写法:利用 push_back 和 reserve 实现
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
reserve(v.size());
for (const auto& e : v)
{
push_back(e);
}
}
优点:
避免重复操作:利用 push_back 直接将元素插入,避免了逐个赋值的重复操作。
缺点:
性能问题:使用 push_back 可能会多次分配内存和释放内存,性能较差。
可能出现异常:如果 push_back 中的内存分配失败,可能会导致异常。
第三种写法:利用临时 vector 进行交换
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
vector<T> tmp(v.begin(), v.end());
swap(tmp);
}
这种写法要建立在另一种迭代器类型的拷贝构造。
优点:
交换技巧:通过构造一个临时 vector,然后将当前 vector 和临时 vector 进行交换,避免了重复内存操作。
缺点:
需要额外内存:需要额外的内存来构造临时 vector。
不直观:可能不太直观,需要理解 swap 的交换机制。
综合考虑,第三种写法可能是最佳的选择,因为它避免了重复内存操作,同时通过利用 swap 技巧来保证异常安全性。然而,第二种写法也是一个不错的选择,特别是在不要求高性能的情况下,代码清晰易懂。至于第一种写法,由于可能出现内存泄漏和异常不安全问题,可能并不推荐使用。
template <class InputIterator>
vector(InputIterator first, InputIterator last)
template <class InputIterator>
vector(InputIterator first, InputIterator last)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
while (first != last)
{
push_back(*first);
++first;
}
}
这个构造函数模板实现了通过迭代器范围初始化 vector。它遍历输入的迭代器范围,将每个元素通过 push_back 插入到 vector 中。这种方式非常适合处理容器的初始化,无论容器中元素的数量和类型如何,都可以通过迭代器范围方便地初始化一个新的 vector。这种构造函数利用了迭代器提供的通用性,使得代码更加灵活,可以适用于各种元素类型,包括内置类型和用户自定义类型。这个构造函数模板充分展示了 C++ 中的泛型编程思想,使得代码可以适用于不同的情境和数据类型。
上一种拷贝构造复用了此类迭代器拷贝构造。
vector(size_t n, const T& val = T())
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。它接受两个参数,第一个参数是初始化的元素数量 n,第二个参数是元素的初始值 val。这种构造函数的作用是创建一个包含了 n 个相同值 val 的 vector。通过循环将相同值插入容器中,实现了快速初始化相同值的需求。默认情况下,val 的值使用了默认构造函数来初始化,因此可以在调用时省略。
这个构造函数在某些场景下可以提高初始化的效率,尤其是当需要初始化大量相同值的元素时,避免了反复调用 push_back 的开销,而直接在内存中复制值。
但最好加上下面这个版本
vector(int 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> v2(3, 10);
这样调用拷贝构造时,可能会优先调用template <class InputIterator> vector(InputIterator first, InputIterator last)
,因为两个操作数为同一类型,编译器在类中找匹配函数时,会找更相近匹配的,此时会发生错误,所以最好加上下面这个int版本,可能你会疑问,为什么不直接用int版本,这是因为vector拷贝函数的定义就是无符号整形。
3.3.3 析构函数
~vector()
{
delete[] _start; // 释放存储元素的内存块
_start = _finish = _end_of_storage = nullptr; // 将成员指针置为 nullptr
}
在析构函数中,首先使用 delete[] 删除存储元素的内存块 _start,这会释放所有存储在 vector 中的元素所占用的内存。接着,将 _start、_finish、_end_of_storage 这三个成员指针全部置为 nullptr,以避免在析构函数执行完毕后再次调用它们引发问题。
这个析构函数的作用是确保在 vector 对象被销毁时,所占用的内存得到释放,避免内存泄漏。
3.4 容量函数、元素访问函数和增删查改函数
3.4.1 容量函数
capacity()
size_t capacity() const
{
return _end_of_storage - _start;
}
这个 capacity() 成员函数返回的是 vector 内部分配的存储空间的容量,即当前可容纳的元素个数,而不是当前已经存储的元素个数(使用 size() 获取)。这个函数的实现很简单,它返回 _end_of_storage 指针减去 _start 指针所得到的差值,也就是当前分配的存储空间可以容纳的元素个数。这是因为在 vector 的实现中,连续的存储空间被分配在 _start 和 _end_of_storage 之间,所以两者之间的距离就代表了容量。
size()
size_t size() const
{
return _finish - _start;
}
这个 size() 函数用于返回 vector 中元素的个数,它的实现通过计算 _finish 指针和 _start 指针之间的距离(偏移量)来得到。由于 _finish 和 _start 分别指向 vector 内存空间中的元素的尾后位置和起始位置,它们之间的距离就是 vector 中元素的个数。这个函数在 O(1) 的时间复杂度下返回 vector 中的元素个数。
reserve()
void reserve(size_t n)
{
if (n > capacity())
{
size_t sz = size();
T* tmp = new T[n];
if (_start)
{
//memcpy(tmp, _start, sizeof(T)*sz);
for (size_t i = 0; i < sz; ++i)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
这个 reserve(size_t n) 函数用于预留内存空间,确保 vector 至少能容纳 n 个元素,以减少频繁的内存重新分配。如果当前的内存空间不足以容纳 n 个元素,它会重新分配一个新的内存空间,并将原有的元素复制到新的内存中。
这个函数首先检查要求预留的内存是否超过当前的容量(capacity)。如果超过了,它会重新分配一个新的内存空间,并将原有的元素复制到新的内存中。然后更新 _start、_finish 和 _end_of_storage 指针,以反映新的内存布局。
需要注意的是,这个函数不会改变 vector 中元素的个数,只会改变内存的分配情况。
这里使用 for 循环和 memcpy 进行内存复制有一些区别和注意事项:
数据类型限制:memcpy是按字节复制的,不会执行元素类型的构造函数。如果存储的是类对象,可能会导致未预期的行为。使用 for 循环时,会调用元素的拷贝构造函数,确保正确地复制每个元素。
可移植性:memcpy 是一个底层的内存复制函数,其使用可能会受到不同平台的影响。而使用 for 循环是一种更加通用和可移植的方式。
类型安全:使用memcpy 需要确保元素的大小和布局是适合的,否则可能会导致数据损坏。而 for 循环在拷贝每个元素时会确保类型的正确性。
可读性:for 循环更容易理解和维护,可以清晰地看到每个元素的操作。
综上所述,如果是简单的数据类型,使用 memcpy 可能会更高效,但在涉及类对象、复杂数据结构或类型安全性的情况下,使用 for 循环更为安全和推荐
。
resize()
void resize(size_t n, const T& val = T())
{
if (n < size()) {
// 缩小容器大小
for (size_t i = n; i < size(); ++i) {
_start[i].~T(); // 销毁元素
}
_finish = _start + n;
}
else if (n > size()) {
if (n > capacity()) {
// 需要重新分配内存
T* new_start = new T[n];
for (size_t i = 0; i < size(); ++i) {
new (&new_start[i]) T(std::move(_start[i])); // 移动构造元素
_start[i].~T(); // 销毁原来的元素
}
delete[] _start;
_start = new_start;
_finish = _start + size();
_end_of_storage = _start + n;
}
// 构造新添加的元素
for (size_t i = size(); i < n; ++i) {
new (&_start[i]) T(val);
}
_finish = _start + n;
}
// 若 n == size() 则不需要进行任何操作
}
首先,函数检查是否需要缩小容器大小。如果 n 小于当前大小(size()),则会进入缩小容器大小的分支。
在这种情况下,函数会逐个调用 _start[i].~T(),即对应位置元素的析构函数,以销毁多余的元素。然后,将 _finish 指针更新为 _start + n,表示容器只包含前 n 个元素。如果== n 大于当前大小==,那么函数会检查是否需要重新分配内存。如果 n 大于当前容量(capacity()),则会进入重新分配内存的分支。
在这种情况下,函数会创建一个新的内存块 new_start,然后将当前容器中的元素逐个移动构造到新的内存块中,同时析构原来位置的元素。然后,释放原来的内存块 _start,并更新 _start、_finish 和 _end_of_storage 指针。
无论是缩小容器大小还是重新分配内存,函数都会在容器尾部构造新的元素,以填充到 n 个元素。使用 new (&_start[i]) T(val) 来在指定位置构造元素,其中 val 为传入的默认值。然后,将 _finish 指针更新为 _start + n。
3.4.2 元素访问函数
operator[]
const T& operator[](size_t pos) const
{
assert(pos < size());
return _start[pos];
}
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
这两个重载的 operator[] 函数分别用于访问 vector 中指定位置的元素。一个是 const 版本,用于在常量对象上获取元素值,另一个是非 const 版本,用于在非常量对象上获取或修改元素值。这两个函数通过检查索引 pos 是否在合法范围内来保证不越界访问。非 const 版本返回一个非常量引用,这允许用户通过该操作符修改 vector 中的元素值。而 const 版本返回一个常量引用,用于在常量对象上访问元素值,但不允许修改。
front()
T& front()
{
assert(size() > 0);
return *_start;
}
首先,函数使用 assert 宏来检查容器的大小是否大于 0,确保容器中至少有一个元素。
然后,函数返回 _start 指针指向的元素的引用,即容器的第一个元素。
back()
T& back()
{
assert(size() > 0);
return *(_finish - 1);
}
首先,函数使用 assert 宏来检查容器的大小是否大于 0,确保容器中至少有一个元素。
然后,函数返回 _finish - 1 指针指向的元素的引用,即容器的最后一个元素。
3.4.3 增删查改函数
push_back()
void push_back(const T& x)
{
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = x;
++_finish;
//insert(end(), x);
}
首先,函数检查 _finish 指针是否等于 _end_of_storage,即是否达到了当前容器的容量上限。如果达到了容量上限,说明没有足够的空间存放新元素,因此需要进行内存重新分配。
内存重新分配时,使用 reserve 函数,如果当前容量为 0,则分配至少 4 个元素的空间,否则将容量扩展为当前容量的两倍。然后,函数将新元素 x 赋值给 _finish 指针指向的位置,即当前容器的末尾。然后,通过递增 _finish 指针,将 _finish 指向下一个可用位置。
pop_back()
void pop_back()
{
assert(_finish > _start);
--_finish;
}
首先,函数使用 assert 宏来检查 _finish 指针是否大于 _start 指针。这是一个断言,用于确保在 _finish 小于或等于 _start 时不会发生错误。如果 _finish 不大于 _start,则断言会触发错误,帮助在开发过程中发现问题。
接着,函数将 _finish 指针递减 1,从而使其指向容器中的倒数第二个元素,即删除了最后一个元素。
iterator insert()
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(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 + 1) = *end;
--end;
}
*pos = x;
++_finish;
return pos;
}
首先,函数使用 assert 宏来检查 pos 是否在 _start 和 _finish 之间,确保插入位置在合法范围内。然后,函数检查容器是否已满,即 _finish 是否等于 _end_of_storage。如果容器已满,需要扩容。首先计算插入位置相对于 _start 的偏移量 len,以便在插入后重新定位 pos,以防扩容导致迭代器失效。接着根据扩容规则进行扩容操作,将 _start 移动到新分配的内存中。在容器中插入元素之前,需要将插入位置之后的元素往后挪动一个位置。使用一个循环从 _finish - 1 开始,逐个将元素向后移动一位,为新元素腾出空间。将新元素 x 插入到指定的位置 pos。最后,递增 _finish 指针,表示容器中多了一个元素,然后返回插入位置的迭代器。
iterator erase()
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator begin = pos + 1;
while (begin < _finish)
{
*(begin - 1) = *begin;
++begin;
}
--_finish;
return pos;
}
首先,函数使用 assert 宏来检查 pos 是否在 _start 和 _finish 之间,确保删除位置在合法范围内。
然后,函数创建一个名为 begin 的迭代器,初始值为 pos 的下一个位置,即要删除的元素之后的位置。
接着,使用一个循环将 begin 位置及其后面的元素逐个向前移动一个位置,覆盖掉要删除的元素。这样就相当于将删除位置的元素覆盖掉,从而实现删除操作。
最后,递减 _finish 指针,表示容器中少了一个元素,然后返回原始的删除位置的迭代器。
3.4.4 赋值运算符重载
operator=
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
这个赋值运算符重载是一个实现 C++ 中的拷贝交换技术(Copy-and-Swap Idiom)的常见写法,用于实现自定义的赋值操作。该赋值运算符接受一个传值的参数 v,这里通过传值来进行拷贝构造一个临时的 vector 对象,然后调用 swap(v) 实现了将当前对象的数据与临时对象进行交换。这样,临时对象的数据会被赋值给当前对象,而临时对象会在函数结束时被析构,从而释放掉原来当前对象的资源。
这种写法的好处在于它可以避免拷贝构造和析构造成的额外开销,从而提高了效率。同时,交换操作的实现通常在类内部对各个成员进行指针交换,因此不会涉及数据的实际拷贝。
总的来说,这个赋值运算符实现了一种高效的赋值操作,是 C++ 中常用的实现方式之一。文章来源:https://www.toymoban.com/news/detail-629887.html
vector模拟实现全部代码
#pragma once
#include <assert.h>
#include <iostream>
namespace bit
{
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
vector()
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{}
//vector(const vector<T>& v)
//{
// _start = new T[v.size()]; // v.capacity()也可以
// //memcpy(_start, v._start, sizeof(T)*v.size());
// for (size_t i = 0; i < v.size(); ++i)
// {
// _start[i] = v._start[i];
// }
// _finish = _start + v.size();
// _end_of_storage = _start + v.size();
//}
/*vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
reserve(v.size());
for (const auto& e : v)
{
push_back(e);
}
}*/
vector(int 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(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);
}
}
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>& v)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
vector<T> tmp(v.begin(), v.end());
swap(tmp);
}
// v1 = v2
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
~vector()
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
size_t capacity() const
{
return _end_of_storage - _start;
}
const T& operator[](size_t pos) const
{
assert(pos < size());
return _start[pos];
}
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
size_t size() const
{
return _finish - _start;
}
void reserve(size_t n)
{
if (n > capacity())
{
size_t sz = size();
T* tmp = new T[n];
if (_start)
{
//memcpy(tmp, _start, sizeof(T)*sz);
for (size_t i = 0; i < sz; ++i)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
void resize(size_t n, const T& val = T())
{
if (n < size()) {
// 缩小容器大小
for (size_t i = n; i < size(); ++i) {
_start[i].~T(); // 销毁元素
}
_finish = _start + n;
}
else if (n > size()) {
if (n > capacity()) {
// 需要重新分配内存
T* new_start = new T[n];
for (size_t i = 0; i < size(); ++i) {
new (&new_start[i]) T(std::move(_start[i])); // 移动构造元素
_start[i].~T(); // 销毁原来的元素
}
delete[] _start;
_start = new_start;
_finish = _start + size();
_end_of_storage = _start + n;
}
// 构造新添加的元素
for (size_t i = size(); i < n; ++i) {
new (&_start[i]) T(val);
}
_finish = _start + n;
}
// 若 n == size() 则不需要进行任何操作
}
void push_back(const T& x)
{
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = x;
++_finish;
//insert(end(), x);
}
void pop_back()
{
assert(_finish > _start);
--_finish;
}
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(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 + 1) = *end;
--end;
}
*pos = x;
++_finish;
return pos;
}
// stl 规定erase返回删除位置下一个位置迭代器
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator begin = pos + 1;
while (begin < _finish)
{
*(begin - 1) = *begin;
++begin;
}
--_finish;
return pos;
}
T& front()
{
assert(size() > 0);
return *_start;
}
T& back()
{
assert(size() > 0);
return *(_finish - 1);
}
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
}
结语
有兴趣的小伙伴可以关注作者,如果觉得内容不错,请给个一键三连吧,蟹蟹你哟!!!
制作不易,如有不正之处敬请指出
感谢大家的来访,UU们的观看是我坚持下去的动力
在时间的催化剂下,让我们彼此都成为更优秀的人吧!!!文章来源地址https://www.toymoban.com/news/detail-629887.html
到了这里,关于C++初阶之一篇文章让你掌握vector(模拟实现)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!