C++初阶之一篇文章让你掌握vector(模拟实现)

在这里插入图片描述

1.为什么要模拟实现vector?

在这里插入图片描述

模拟实现vector是为了深入理解和学习C++标准库中vector容器的工作原理和实现细节。
vector是C++标准库中最常用的容器之一,它提供了动态数组的功能,并且具有自动扩容和内存管理的特性,使得在使用时非常方便。

模拟实现vector有以下几个优点:

  1. 学习数据结构与算法:实现vector需要涉及到动态数组的增删查改等操作,这可以让开发者学习到关于数据结构和算法的相关知识。
  2. 理解内存管理:vector需要管理动态内存分配和释放,了解如何使用指针、动态分配内存以及防止内存泄漏等问题是很重要的。
  3. 理解模板编程:vector是一个通用的容器,可以存储不同类型的元素。模拟实现vector需要涉及到模板编程,让开发者更好地理解C++中的模板机制。
  4. 提高编程技巧:通过实现vector,开发者可以锻炼自己的编程技巧和代码设计能力,同时也可以发现一些潜在的问题和改进的空间。

虽然C++标准库中已经提供了vector容器,但是模拟实现vector对于学习和理解C++的底层实现以及算法和数据结构有很大的帮助。实际开发中,我们通常使用标准库中的vector,因为它已经经过了充分的测试和优化,具有更好的性能和稳定性。然而,通过模拟实现vector,我们可以更好地理解背后的原理和设计思想。

2.模拟实现vector需要注意哪些问题?

在模拟实现vector时,需要注意以下几个关键问题:

  1. 动态内存管理:vector是动态数组,需要在运行时动态分配内存。要确保在插入、删除元素时正确地分配和释放内存,避免内存泄漏和悬挂指针的问题
  2. 自动扩容:vector具有自动扩容的功能,当容器中的元素数量超过当前容量时,需要重新分配更大的内存空间,并将原有的元素拷贝到新的内存空间中
  3. 模板编程:vector是一个通用的容器,可以存储不同类型的元素。因此,在模拟实现vector时需要使用模板编程,确保容器可以适用于不同类型的数据。
  4. 迭代器:vector应该提供迭代器用于访问容器中的元素。迭代器应该支持指针的各种操作,如指针算术、解引用等。
  5. 成员函数和接口:模拟实现的vector应该提供类似于标准库中vector的成员函数和接口,例如push_back、pop_back、size、capacity等等。
  6. 异常安全性:在模拟实现vector的过程中,要确保对异常的处理,避免因为异常而导致内存泄漏或数据结构被破坏。
  7. 性能优化:模拟实现的vector可能不如标准库中的vector性能优越,但是仍然要考虑一些性能优化的方法,例如避免频繁的内存分配和释放、减少不必要的拷贝等。
  8. 测试:在实现过程中,要进行充分的测试,确保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++ 中常用的实现方式之一。

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们的观看是我坚持下去的动力
在时间的催化剂下,让我们彼此都成为更优秀的人吧!!!

猜你喜欢

转载自blog.csdn.net/kingxzq/article/details/132104190
今日推荐