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

这篇具有很好参考价值的文章主要介绍了C++初阶之一篇文章让你掌握vector(模拟实现)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

C++初阶之一篇文章让你掌握vector(模拟实现),C++初阶,c++,开发语言

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

C++初阶之一篇文章让你掌握vector(模拟实现),C++初阶,c++,开发语言

模拟实现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拷贝函数的定义就是无符号整形。
C++初阶之一篇文章让你掌握vector(模拟实现),C++初阶,c++,开发语言

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们的观看是我坚持下去的动力
在时间的催化剂下,让我们彼此都成为更优秀的人吧!!!文章来源地址https://www.toymoban.com/news/detail-629887.html

到了这里,关于C++初阶之一篇文章让你掌握vector(模拟实现)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++初阶之一篇文章教会你list(理解和使用)

    在C++标准库中, std::list 是一个双向链表容器,用于存储一系列元素。与 std::vector 和 std::deque 等容器不同, std::list 使用链表的数据结构来组织元素,因此在某些操作上具有独特的优势和性能特点。以下是关于 std::list 的详细介绍: 双向链表结构: std::list 内部使用双向链表来

    2024年02月13日
    浏览(51)
  • 一篇文章让你完全掌握使用Git推送代码到新版GitCode

    GitCode是一款基于Git的在线代码托管和协作工具,提供代码版本控制、代码托管、代码评审、项目管理等功能。它支持多种编程语言,包括 Java 、 Python 、 C++ 等,可帮助开发者高效协作,提高代码质量和开发效率。GitCode还提供丰富的 API 接口,支持与其他系统集成,方便开发

    2024年03月25日
    浏览(43)
  • 谁说配置难?这篇文章让你轻松掌握xilinx 7系列FPGA配置技巧

      本文旨在通过讲解不同模式的原理图连接方式,进而配置用到引脚的含义(手册上相关引脚含义有四、五页,通过本文理解基本上能够记住所有引脚含义以及使用场景),熟悉xilinx 7系列配置流程,以及设计原理图时需要注意的一些事项,比如flash与FPGA的上电时序。   x

    2024年02月06日
    浏览(70)
  • Spring Boot 3 + JWT + Security 联手打造安全帝国:一篇文章让你掌握未来!

    Spring Security 已经成为 java 后台权限校验的第一选择.今天就通过读代码的方式带大家深入了解一下Security,本文主要是基于开源项目spring-boot-3-jwt-security来讲解Spring Security + JWT(Json Web Token).实现用户鉴权,以及权限校验. 所有代码基于 jdk17+ 构建.现在让我们开始吧! Springboot 3.0 Spri

    2024年02月07日
    浏览(75)
  • 【数据结构——顺序表】线性表很难嘛?这篇文章能让你轻松掌握顺序表

    线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…。线性表在逻辑上是线性结构,也就是说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式的结构的形式存储。 是n个具有相

    2024年02月07日
    浏览(55)
  • 【C++】一篇文章带你深入了解vector

    vector的文档介绍 vector是表示可变大小数组的序列容器。 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。 本质讲,

    2024年04月22日
    浏览(50)
  • 【C++算法图解专栏】一篇文章带你掌握差分算法

    ✍个人博客:https://blog.csdn.net/Newin2020?spm=1011.2415.3001.5343 📣专栏定位:为 0 基础刚入门数据结构与算法的小伙伴提供详细的讲解,也欢迎大佬们一起交流~ 📚专栏地址:https://blog.csdn.net/Newin2020/article/details/126445229 ❤️如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创

    2024年04月11日
    浏览(44)
  • 一篇文章让你搞懂内存函数

    库函数memcmp介绍 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。 这个函数在遇到 ‘\\0’ 的时候并不会停下来。 如果source和destination有任何的重叠,复制的结果都是未定义的。 库函数memcmp的代码形式 看代码 memcmp将arr1中的内容拷贝到arr2中,总共

    2024年02月17日
    浏览(45)
  • 读这篇文章让你彻底了解Redis

    你好,我是Redis,一个叫Antirez的男人把我带到了这个世界上。 说起我的诞生,跟关系数据库MySQL还挺有渊源的。 在我还没来到这个世界上的时候,MySQL过的很辛苦,互联网发展的越来越快,它容纳的数据也越来越多,用户请求也随之暴涨,而每一个用户请求都变成了对它的一

    2024年02月04日
    浏览(46)
  • 一篇文章让你读懂-曼彻斯特编码

    目录 写在前面的话 1 what?什么是曼彻斯特编码  2 how?怎么使用曼彻斯特编码 2.1 曼彻斯特的编码: 2.2 曼彻斯特的译码: 3 why?为什么推荐曼彻斯特编码?这种编码方式的优缺点         数据传输之前为什么将数据进行编码?         这是个好问题!!         一

    2023年04月15日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包