首页 > 编程语言 >如何实现标准库般强大的 C++ Vector?:从动态扩容到移动语义到迭代器全覆盖

如何实现标准库般强大的 C++ Vector?:从动态扩容到移动语义到迭代器全覆盖

时间:2024-09-09 12:23:54浏览次数:12  
标签:finish back C++ start 器全 Vector push size

在 C++ 中,std::vector 是最常用的动态数组容器。它具有高效的内存管理、动态扩容、随机访问等特点。在这篇博客中,我们将从零开始,实现一个功能强大、灵活、并且具有高性能的 Vector 类,具备 std::vector 的大部分功能,包括动态扩容、迭代器、模板支持、随机访问等,尽可能模仿 C++ 标准库中的 std::vector。本文将详细解释每个功能模块,并附加详细的解释和代码注释,以帮助初学者深入理解容器的内部工作原理和实现逻辑。


一、设计目标和规划

在设计一个 Vector 类时,我们需要考虑以下功能:

  • 动态扩容:支持自动增长容量,避免内存溢出。
  • 模板支持:容器应该能够存储任意类型的数据。
  • 迭代器:提供正向和反向迭代器,使得容器可以通过迭代器进行遍历。
  • 边界检查:防止访问越界,提高容器的健壮性。
  • 性能优化:通过使用移动语义和完美转发提升性能,减少不必要的拷贝。
  • 异常安全:确保在操作失败时不出现内存泄漏等问题。

让我们从基础的 Vector 实现开始,逐步添加这些功能。

注意:这篇博客所涉及的所有代码可以从我的代码仓库获得:https://git.lenyiin.com/Lenyiin/Vector


二、Vector 容器的基础实现

首先,我们需要定义一个 Vector 类来管理内部的数据。这个类包含动态分配的数组,维护当前元素数量和容量,并且提供基础的操作方法,比如插入、访问等。


2.1、Vector 容器的成员的设计与初始化

首先,我们定义一个 Vector 类的基础框架,它需要包括以下几个核心成员变量:

  • _start:指向存储实际元素的数组首地址。
  • _finish:指向存储实际元素的数组下一个可存储的地址。
  • _endofstorage:当前已分配的内存空间的末地址。
template<typename T>
class Vector {
public:
    // 默认构造
    Vector()	// 构造函数,初始化为空
        : _start(nullptr), _finish(nullptr), _endofstorage(nullptr)
    {
    }

    // 析构函数
    ~Vector()	// 析构函数,释放动态内存
    {
        if (_start)
        {
            delete[] _start;
        }
        _start = _finish = _endofstorage = nullptr;
    }

private:
	T* _start;
	T* _finish;
	T* _endofstorage;
};

详细解释:

  • 构造函数:在构造函数中,初始状态下 _start 为空,_finish 为 空,_endofstorage 也为空。
  • 析构函数:确保当 Vector 对象销毁时释放分配的内存。

2.2、获取容器状态

为了更好的了解容器状态,还需要添加获取容器状态的接口

// 获取当前元素有效个数
size_t size() const
{
	return _finish - _start;
}

// 获取当前容器容量
size_t capacity() const
{
	return _endofstorage - _start;
}

详细解释:

  • size()capacity() :分别返回当前的元素个数和容器容量,帮助用户了解容器的状态。

2.3、拷贝与赋值

C++ 中,拷贝一个对象时,默认的拷贝行为是浅拷贝,即仅复制对象的指针或引用。这对管理动态内存的类而言,可能会导致问题,例如多个对象指向同一块内存,导致重复释放或修改冲突。为避免这些问题,我们需要实现深拷贝。

2.3.1、拷贝构造函数

拷贝构造函数用于创建一个新对象,该对象是通过复制另一个现有对象生成的。对于 Vector 类,我们需要确保在拷贝时,新对象有自己独立的内存副本。

// 拷贝构造
Vector(const Vector<T>& v)
{
	_start = new T[v.capacity()];
	_finish = _start;
	_endofstorage = _start + v.capacity();

	for (size_t i = 0; i < v.size(); i++)
	{
		*_finish = v[i];
		++_finish;
	}
}

详细解释:

  • 深拷贝:通过分配新内存来创建新对象的独立副本,而不是简单地复制指针。这样,两个 Vector 对象可以独立管理各自的内存,避免潜在的内存管理冲突。
  • 值得注意的是 *_finish = v[i]; 并不是浅拷贝,而是调用了 operator= 赋值运算,是深拷贝。

进阶:

  • 也可以复用插入函数 push_back
Vector(const Vector<T>& v)
	: _start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{
	reserve(v.capacity());
	for (const auto& e : v)
	{
		push_back(e);
	}
}

2.3.2、赋值运算

赋值运算符用于将一个对象的内容复制到另一个已经存在的对象中。为了避免自赋值和内存泄漏,我们需要在实现赋值运算符时特别小心。

// 赋值运算符重载
Vector<T>& operator=(const Vector<T>& v)
{
	if (this != &v)
	{
		delete[] _start;
		_start = new T[v.capacity()];

		//memcpy(_start, v._start, sizeof(T) * v.size());	按字节拷贝,浅拷贝
		for (size_t i = 0; i < v.size(); i++)
		{
			_start[i] = v._start[i];	// 调用的是 operator= 深拷贝
		}

		_finish = _start + v.size();
		_endofstorage = _start + v.capacity();
	}
	return *this;
}

详细解释:

  • 自赋值检查:在赋值运算符实现中,首先检查是否为自赋值,即 this 指针是否与 v 相同。如果是自赋值,则无需进行任何操作,直接返回当前对象。
  • 内存管理:在分配新内存之前,记得释放当前对象所持有的旧内存,防止内存泄漏。
  • 深拷贝:与拷贝构造函数类似,通过分配新内存来存储字符串的副本,确保两个对象独立管理各自的内存。

进阶:

  • 也可以复用拷贝构造函数
// 进阶写法
void swap(Vector<T>& v)
{
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_endofstorage, v._endofstorage);
}

Vector<T>& operator=(Vector<T> v)
{
	swap(v);
	return *this;
}

2.4、移动语义

C++11 引入的移动语义(Move Semantics)是提升性能的一个重要机制,允许在一定条件下避免不必要的深拷贝,从而提高程序性能。移动构造函数和移动赋值运算符是移动语义的核心。

2.4.1、移动构造函数

移动构造函数用于将资源从一个对象 “搬迁” 到另一个对象,而不是复制。这在避免不必要的内存分配和拷贝时非常有用。

// 移动构造函数
Vector(Vector&& v) noexcept 
	: _start(v._start), _finish(v._finish), _endofstorage(v._endofstorage)
{
    v._start = nullptr;
    v._finish = nullptr;
    v._endofstorage = nullptr;
}

详细解释:

  • 移动构造函数:将 v 对象的资源直接转移到当前对象(通过简单地复制指针),然后将 v_start, _finish, _endofstorage 重置为默认状态。通过窃取资源(即指针和相关的大小和容量),避免昂贵的拷贝操作。

  • noexcept:标记为 noexcept 的函数表示在其内部不会抛出异常。这对移动构造函数尤其重要,因为这确保了在某些情况下(如在标准容器中使用)不会因为抛出异常而触发回滚操作。

2.4.2、移动赋值运算

移动赋值运算用于将资源从一个对象 “搬迁” 到另一个已存在的对象中。

// 移动赋值运算符
Vector& operator=(Vector&& v) noexcept 
{
    if (this != &v) {
        delete[] _start;
        _start = v._start;
        _finish = v._start + v.size();
        _endofstorage = v._start + v.capacity();
        v._start = nullptr;
        v._finish = nullptr;
        v._endofstorage = nullptr;
    }
    return *this;
}

详细解释:

  • 资源搬迁:通过简单地复制指针,将 v 对象的资源转移到当前对象中,并释放当前对象的旧资源。然后,将 v 对象重置为默认状态,避免两个对象共享同一块内存。
  • 自赋值检查:和赋值运算符一样,首先检查是否为自赋值。

2.5、下标运算符重载

为了支持像数组一样通过下标访问 Vector 中的元素,我们需要重载 operator[]

T& operator[](size_t index) 
{
    if (index >= size()) 
    {
    	throw std::out_of_range("Index out of range");
    }
    return _start[index];
}

const T& operator[](size_t index) const 
{
    if (index >= size()) 
    {
    	throw std::out_of_range("Index out of range");
    }
    return _start[index];
}

详细解释:

  1. operator[] 重载:支持通过下标访问元素,并且提供 const 版本,确保容器在只读情况下的访问安全性。
  2. 越界检查:若访问的下标超出范围,抛出 std::out_of_range 异常,避免非法访问。

三、进阶功能

3.1、动态扩容

C++ 中 std::vector 最强大的功能之一是动态扩容。当容器中的元素超过当前容量时,它会自动分配更大的内存,并将已有的数据复制到新的内存区域。为此,我们实现一个 reserve 和 resize 函数,用于在需要时扩容。

void reserve(size_t newcapacity)
{
	if (newcapacity > capacity())
	{
		T* tmp = new T[newcapacity];	// 分配新内存
		size_t len = size();
		if (_start)
		{
			for (size_t i = 0; i < len; i++)
			{
				tmp[i] = std::move(_start[i]);	// 移动已有数据
			}
			delete[] _start;	// 释放旧的内存
		}
		// 更新指针
		_start = tmp;
		_finish = tmp + len;
		_endofstorage = tmp + newcapacity;
	}
}

// resize 不仅会开空间,还会初始化
void resize(size_t newsize, const T& val = T())
{
	if (newsize < size())
	{
		_finish = _start + newsize;
	}
	else
	{
		if (newsize > capacity())
		{
			reserve(newsize);
		}

		while (_finish < _start + newsize)
		{
			*_finish = val;
			++_finish;
		}
	}
}

详细解释:

  • reserve 函数:用于重新分配内存,当当前容量不足时,分配一个新的数组并将原有数据复制过去。
  • resize 函数:用于重置有效数据大小,重置的时候还能进行初始化。当当前容量不足时,分配一个新的数组并将原有数据复制过去。
  • std::move:我们使用 std::move 来避免不必要的深度拷贝,提升性能。

3.2、插入元素 push_back

Vector 中添加元素时,需要判断是否需要扩容。如果当前元素数量已经等于容量,就调用 reserve 函数扩容。

void push_back(const T& data)
{
	if (_finish == _endofstorage)	// 若容量不足,则扩容
	{
		size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);
	}

	*_finish = data;	// 插入新元素
	++_finish;
}

详细解释:

  • push_back:当 _finish == _endofstorage 时,调用 reserve 扩容,然后插入新元素。

  • reserve 容量扩充策略:当容器为空时,初次扩容至 4,随后每次扩容翻倍。


3.3、增加迭代器支持

3.3.1、基础迭代器

std::vector 提供了强大的迭代器支持,允许用户使用 for-each 循环来遍历容器。我们可以通过自定义迭代器类,来实现这一功能。

typedef T* iterator;

iterator begin()
{
	return _start;
}

iterator end()
{
	return _finish;
}

详细解释:

  • iterator :封装了指针,使用默认解引用操作符 * 和递增操作符 ++,支持 for 循环遍历。

  • begin 与 end 函数:返回指向容器起始和末尾的迭代器,类似 std::vector::beginstd::vector::end

3.3.2、常量迭代器

在一些场景中,我们希望容器只能读取而不能修改数据。为此,我们可以实现 const_iterator 来保证只读遍历。

typedef const T* const_iterator;

const_iterator begin() const
{
	return _start;
}

const_iterator end() const
{
	return _finish;
}

详细解释:

  • const_iterator:与普通迭代器不同的是,它返回 const T*,确保数据不能被修改。

  • begin 与 end 函数:返回常量迭代器,支持只读遍历。


3.4、删除元素与清空操作

为了使我们的 Vector 容器更加完整,我们还需要支持删除元素和清空容器的操作。

void pop_back()
{
	assert(_start < _finish);

	--_finish;
}

void clear()
{
	_finish = _start;
}

详细解释:

  • pop_back 函数:移除最后一个元素,减小 _finish,但不改变已分配的容量。
  • clear 函数:将 _finish 设为 _start,保留容量但清空元素。

四、扩展功能

4.1、插入与删除

标准库的 std::vector 还支持在中间位置插入和删除元素。我们可以通过移动元素来实现这些功能。

iterator insert(iterator pos, const T& data)
{
	assert(pos <= _finish);

	if (_finish == _endofstorage)
	{
		size_t len = pos - _start;
		size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);

		// 如果增容,原来的pos就失效了,这里需要重新计算位置
		pos = _start + len;
	}

	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}

	*pos = data;
	++_finish;

	return pos;
}

iterator erase(iterator pos)
{
	assert(pos < _finish);

	iterator it = pos;
	while (it < _finish)
	{
		*it = *(it + 1);
		++it;
	}
	--_finish;

	return pos;
}

详细解释:

  • insert 函数:在指定位置插入元素,插入后,所有后续元素向后移动一位。
  • erase 函数:删除指定位置的元素,删除后,所有后续元素向前移动一位。

4.2、模板支持

我们通过使用模板,使 Vector 能够存储任意类型的元素。C++ 的模板机制允许我们实现对不同数据类型的支持,而无需为每种类型编写单独的代码。在上面的代码中,我们已经通过 template<typename T> 实现了模板支持。只需传递类型参数,即可为任何类型创建 Vector 容器。

Vector<int> intVector;
Vector<std::string> stringVector;

4.3、使用移动语义与完美转发

在 C++11 中引入的移动语义和完美转发是提升性能的重要工具,特别是在处理对象拷贝时。为了避免不必要的深度拷贝,我们可以使用 std::movestd::forward

void push_back(T&& data) 	// 移动版本
{ 
    if (_finish == _endofstorage) 
    {
    	reserve(capacity() == 0 ? 4 : capacity() * 2);
    }
    *_finish = std::move(data); // 使用 move 语义
    ++_finish;
}

详细解释:

  • push_back (移动版本):允许直接移动元素,而不是进行拷贝操作,大大提升了效率。

4.4、 完善边界检查与防止越界访问

为了使容器更加安全,我们可以在访问元素时进行边界检查,防止越界访问导致程序崩溃。

T& at(size_t index) 
{
    if (index >= size()) {
    	throw std::out_of_range("Index out of bounds");
    }
    return _start[index];
}

详细解释:

  • at 函数:提供安全的数组访问方法,超出范围时抛出 std::out_of_range 异常。

五、完整实现代码和测试

5.1、Vector.hpp

新建头文件 Vector.hpp

#pragma once

#include <iostream>
#include <assert.h>

using namespace std;

namespace Lenyiin
{
	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;
		}

		size_t size() const
		{
			return _finish - _start;
		}

		size_t capacity() const
		{
			return _endofstorage - _start;
		}

		// 默认构造
		Vector()
			: _start(nullptr), _finish(nullptr), _endofstorage(nullptr)
		{
		}

		// 拷贝构造
		//Vector(const Vector<T>& v)
		//{
		//	_start = new T[v.capacity()];
		//	_finish = _start;
		//	_endofstorage = _start + v.capacity();

		//	for (size_t i = 0; i < v.size(); i++)
		//	{
		//		*_finish = v[i];
		//		++_finish;
		//	}
		//}

		// 进阶写法
		Vector(const Vector<T>& v)
			: _start(nullptr), _finish(nullptr), _endofstorage(nullptr)
		{
			reserve(v.capacity());
			for (const auto& e : v)
			{
				push_back(e);
			}
		}

		// 赋值运算符重载
		//Vector<T>& operator=(const Vector<T>& v)
		//{
		//	if (this != &v)
		//	{
		//		delete[] _start;
		//		_start = new T[v.capacity()];

		//		//memcpy(_start, v._start, sizeof(T) * v.size());	按字节拷贝,浅拷贝
		//		for (size_t i = 0; i < v.size(); i++)
		//		{
		//			_start[i] = v._start[i];	// 调用的是 operator= 深拷贝
		//		}

		//		_finish = _start + v.size();
		//		_endofstorage = _start + v.capacity();
		//	}
		//	return *this;
		//}

		// 进阶写法
		void swap(Vector<T>& v)	// 自己写的swap是浅拷贝,代价小
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endofstorage, v._endofstorage);
		}

		Vector<T>& operator=(Vector<T> v)
		{
			swap(v);	// 库里面的swap是深拷贝,代价极大
			return *this;
		}

		// 移动构造函数
		Vector(Vector&& v) noexcept
			: _start(v._start), _finish(v._finish), _endofstorage(v._endofstorage)
		{
			v._start = nullptr;
			v._finish = nullptr;
			v._endofstorage = nullptr;
		}

		// 移动赋值运算符
		Vector& operator=(Vector&& v) noexcept
		{
			if (this != &v) {
				delete[] _start;
				_start = v._start;
				_finish = v._start + v.size();
				_endofstorage = v._start + v.capacity();
				v._start = nullptr;
				v._finish = nullptr;
				v._endofstorage = nullptr;
			}
			return *this;
		}

		// 析构函数
		~Vector()
		{
			if (_start)
			{
				delete[] _start;
			}
			_start = _finish = _endofstorage = nullptr;
		}

		// 下标运算符重载
		T& operator[](size_t index)
		{
			if (index >= size())
			{
				throw std::out_of_range("Index out of range");
			}
			return _start[index];
		}

		const T& operator[](size_t index) const
		{
			if (index >= size())
			{
				throw std::out_of_range("Index out of range");
			}
			return _start[index];
		}

		// 动态扩容
		void reserve(size_t newcapacity)
		{
			if (newcapacity > capacity())
			{
				T* tmp = new T[newcapacity];	// 分配新内存
				size_t len = size();
				if (_start)
				{
					for (size_t i = 0; i < len; i++)
					{
						tmp[i] = std::move(_start[i]);	// 移动已有数据
					}
					delete[] _start;	// 释放旧的内存
				}
				// 更新指针
				_start = tmp;
				_finish = tmp + len;
				_endofstorage = tmp + newcapacity;
			}
		}

		// resize 不仅会开空间,还会初始化
		void resize(size_t newsize, const T& val = T())
		{
			if (newsize < size())
			{
				_finish = _start + newsize;
			}
			else
			{
				if (newsize > capacity())
				{
					reserve(newsize);
				}

				while (_finish < _start + newsize)
				{
					*_finish = val;
					++_finish;
				}
			}
		}

		void push_back(const T& data)
		{
			if (_finish == _endofstorage)
			{
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
			}

			*_finish = data;
			++_finish;
		}

		// 移动版本
		void push_back(T&& data)
		{
			if (_finish == _endofstorage)
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}
			*_finish = std::move(data); // 使用 move 语义
			++_finish;
		}

		// 更安全的访问方式
		T& at(size_t index)
		{
			if (index >= size()) {
				throw std::out_of_range("Index out of bounds");
			}
			return _start[index];
		}

		void pop_back()
		{
			assert(_start < _finish);

			--_finish;
		}

		void clear()
		{
			_finish = _start;
		}

		iterator insert(iterator pos, const T& data)
		{
			assert(pos <= _finish);

			if (_finish == _endofstorage)
			{
				size_t len = pos - _start;
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);

				// 如果增容,原来的pos就失效了,这里需要重新计算位置
				pos = _start + len;
			}

			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}

			*pos = data;
			++_finish;

			return pos;
		}

		iterator erase(iterator pos)
		{
			assert(pos < _finish);

			iterator it = pos;
			while (it < _finish)
			{
				*it = *(it + 1);
				++it;
			}
			--_finish;

			return pos;
		}

	private:
		iterator _start;
		iterator _finish;
		iterator _endofstorage;
	};
}

5.2、Vector.cpp

新建测试文件 Vector.cpp

#include "Vector.hpp"
#include <string>

using namespace Lenyiin;

void print_Vector(const Vector<int>& v)
{
    Vector<int>::const_iterator it = v.begin();
    while (it != v.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;
}

// Vector 容器遍历
void test1()
{
	Vector<int> v;

	// 插入
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    v.push_back(5);

    // 查看容量
    cout << v.size() << endl;
    cout << v.capacity() << endl << endl;;

    // 1. 下标运算符 [] 遍历
    cout << "1. 下标运算符 [] 遍历 \t\t";
    for (size_t i = 0; i < v.size(); i++)
    {
        cout << v[i] << " ";
    }
    cout << endl;

    // 2. 迭代器遍历
    cout << "2. 迭代器遍历 \t\t\t";
    Vector<int>::iterator it = v.begin();
    while (it != v.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    // 3. const_iterator 迭代器遍历
    cout << "3. const_iterator 迭代器遍历 \t";
    print_Vector(v);

    // 4. 范围 for 遍历
    cout << "4. 范围 for 遍历 \t\t";
    for (auto& e : v)
    {
        cout << e << " ";
    }
    cout << endl;

    // 4. 范围 for 遍历 const
    cout << "5. 范围 for 遍历 const \t\t";
    for (const auto& e : v)
    {
        cout << e << " ";
    }
    cout << endl;
}

void test2()
{
    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);
    print_Vector(v);
    cout << v.size() << endl;
    cout << v.capacity() << endl << endl;;

    // 随即插入
    v.insert(v.begin(), 0);
    print_Vector(v);

    // 删除所有偶数
    Vector<int>::iterator it = v.begin();
    while (it != v.end())
    {
        if (*it % 2 == 0)
        {
            it = v.erase(it);
        }
        else
        {
            it++;
        }
    }
    print_Vector(v);
}

void test3()
{
    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);
    v.push_back(7);

    print_Vector(v);
    cout << v.size() << endl;
    cout << v.capacity() << endl << endl;;

    // resize
    v.resize(4);
    print_Vector(v);
    cout << v.size() << endl;
    cout << v.capacity() << endl << endl;;

    v.resize(8);
    print_Vector(v);
    cout << v.size() << endl;
    cout << v.capacity() << endl << endl;;

    v.resize(15);
    print_Vector(v);
    cout << v.size() << endl;
    cout << v.capacity() << endl << endl;;

    // 清除
    v.clear();
    print_Vector(v);
    cout << v.size() << endl;
    cout << v.capacity() << endl << endl;;

    // reserve
    v.reserve(20);
    print_Vector(v);
    cout << v.size() << endl;
    cout << v.capacity() << endl << endl;;
}

void test4()
{
    // 默认构造
    Vector<int> v1;
    v1.push_back(1);
    v1.push_back(2);
    v1.push_back(3);
    v1.push_back(4);
    v1.push_back(5);
    v1.push_back(6);
    v1.push_back(7);
    print_Vector(v1);

    // 拷贝构造
    Vector<int> v2(v1);
    print_Vector(v2);

    Vector<int> v3;
    v3.push_back(10);
    v3.push_back(20);
    v3.push_back(30);
    v3.push_back(40);

    // 赋值
    v1 = v3;
    print_Vector(v1);
    print_Vector(v3);
}

void test5()
{
    // 模板
    Vector<string> v;
    v.push_back("111");
    v.push_back("222");
    v.push_back("333");
    v.push_back("444");

    for (auto e : v)
    {
        cout << e << " ";
    }
    cout << endl;
}

int main()
{
    //test1();
    //test2();
    //test3();
    test4();
    //test5();

    return 0;
}

六、总结

通过这篇博客,我们从最基础的存储与管理机制开始,逐步构建了一个强大且灵活的 Vector 容器,具备动态扩容、模板支持、迭代器、常量迭代器、移动语义、边界检查等高级功能,能够媲美 std::vector。在这一过程中,我们学会了如何高效管理内存、优化性能并保证容器的安全性。此外,我们还增加了随机访问、边界检查、插入删除、异常处理等高级功能。通过这篇文章的学习,你应该对 C++ 容器的内部实现有了更深入的理解,并掌握了构建自定义容器的关键技术。



希望这篇博客对您有所帮助,也欢迎您在此基础上进行更多的探索和改进。如果您有任何问题或建议,欢迎在评论区留言,我们可以共同探讨和学习。更多知识分享可以访问我的个人博客网站 : https://blog.lenyiin.com/ 。本博客所设计的代码也可以访问我的 git 仓库获取 :https://git.lenyiin.com/Lenyiin/Vector

标签:finish,back,C++,start,器全,Vector,push,size
From: https://blog.csdn.net/mmlhbjk/article/details/141999922

相关文章

  • C++里面的iostream是什么东西?
    小弟不才,看了百度的介绍更乱了。。我刚接触c++,我感觉很有意思,今天看c++primer里面介绍过iostream。但是怎么看都不懂。代码里面也出现了#include<iostream>。我想请教一下,iostream是个库,可不可以理解成是一个仓库,里面装的都是C的代码?另外,IO是不是iostream的缩写? C++编译系统提......
  • C++学习笔记(曾经我看不懂的代码2:基于范围的for循环、auto使用、stl容器、template模
    不知不觉c++程序设计:标准库已经看了一大半了,学到了很多,很多曾经在网上和在书上看到却看不懂的代码,在看完标准库中的大半内容以后,都能大致的理清代码的含义。代码模板一:for(auto&a:arr)1、基于范围的for循环:a为迭代变量,arr为迭代范围,&表示引用。写一个例子:#include<ios......
  • C++ iostream、iomanip 头文件详解
    C++iostream、iomanip头文件详解首先,我们来看看iostream。相信大家都知道iostream,这个库可以说是最常用的几个库之一了。iostream库提供了输入输出流。比如cin、cout,都是在iostream里的。所以,我们经常会用到iostream这个库。iostream这个名字很好理解,InputOutputStream,输......
  • C/C++中extern函数使用详解
    extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。此外extern也可用来进行链接指定目录一、定义和声明的区别二、extern用法2.1extern函数2.2extern变量2.3在C++文件中调用C方式编译的函数三、通俗讲......
  • C++ 模板进阶知识——stdenable_if
    目录C++模板进阶知识——std::enable_if1.简介和背景基本语法使用场景2.`std::enable_if`的基本用法示例:限制函数模板只接受整数类型3.SFINAE和std::enable_if示例:使用SFINAE进行函数重载SFINAE的优点应用场景4.在类模板中使用std::enable_if示例:根据类型......
  • 南沙信奥赛C++陈老师解一本通题: 1326:【例7.5】 取余运算(mod)
    ​【题目描述】【输入】输入b,p,k的值。【输出】【输入样例】2109【输出样例】2^10mod9=7 #include<iostream>#include<stdlib.h>usingnamespacestd;longlongb,p,k,ans=1;intmain(){ cin>>b>>p>>k; for(inti=1;i<=p;i++) { ans*=b;......
  • [C++ Daily] 确保类复制了所有应该复制的成员
    确保类复制了所有应该复制的成员结果:源代码:#include<iostream>#include<string>#include<vector>/***copy操作应该包含对象内的所有成员变量及所有父类的成员变量,*此种可以通过调用对应的拷贝构造与拷贝赋值操作完成*////@briefsimpleterminalprint......
  • 南沙信奥赛C++陈老师解一本通题: 1171:大整数的因子
    ​ 【题目描述】已知正整数k满足2≤k≤9,现给出长度最大为30位的十进制非负整数c,求所有能整除c的k。【输入】一个非负整数c,c的位数≤30。【输出】若存在满足 c%k==0的k,从小到大输出所有这样的k,相邻两个数之间用单个空格隔开;若没有这样的k,则输出"none"。【输入样......
  • 【C++学习笔记】数组与指针(三)
    目录一、数组1.1数组声明与赋值1.2数组的特点特点1:任意类型均可创建数组特点2:固定大小特点3:内存连续且有序特点4:C++无数组下标越界错误特点5:数组变量不记录数据1.3遍历数组普通for循环foreach增强循环1.4字符数组1.5多维数组二维数组三维数组遍历二维数......
  • 【C++学习笔记】逻辑判断语句与循环语句(二)
    目录一、逻辑判断语句1.1ifelse语句1.2 switch语句1.3枚举类型二、循环语句2.1while循环2.2dowhile循环2.3for循环2.4break与continue关键字2.5goto语句一、逻辑判断语句1.1ifelse语句#include"iostream"usingnamespacestd;intmain(){......