STL-- C++ vector类 模拟实现

在模拟实现vector核心功能中 我经历了"内存泄漏 ------ 浅拷贝 ------ 迭代器失效 ------ 扩容策略 ------ const 正确性"等一系列只有造轮子才能遇到的坑

终于,在无数次 Segmentation faultdouble free 之后,我完成了属于自己的动态数组

这篇文章将完整记录我的设计思路、关键代码实现、踩过的坑(都已经在代码注释中标出)以及最终的收获

1. 基础管理

  • 构造:默认构造、指定大小和初始值构造、迭代器区间构造、拷贝构造
cpp 复制代码
		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _endOfStorage(nullptr)
		{

		}
		vector(int n, const T& value = T())
			: _start(nullptr)//初始化列表是好习惯
			, _finish(nullptr)//写了是为了更严谨,尤其是当成员在初始化列表中能直接构造时,效率更高;也避免了某些成员(如引用)无法在函数体内赋值的情况
			, _endOfStorage(nullptr)//不论这里写不写 初始化列表总是要走的 写了代码健壮性更高
		{
			T* vec = new T[n];
			for (size_t i = 0; i < n; i++)
			{
				vec[i] = value;
			}
			_start = vec;
			_finish = vec + n;
			_endOfStorage = vec + n;
		}
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
			:_start(nullptr)
			, _finish(nullptr)
			, _endOfStorage(nullptr)
		{
			size_t n = last - first;
			T* vec = new T[n];
			size_t i = 0;
			while (first != last)
			{
				vec[i] = *first;
				first++;
				i++;
			}
			_start = vec;
			_finish = vec + n;
			_endOfStorage = vec + n;
		}
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endOfStorage(nullptr)
		{
			size_t n = v.size();
			T* vec = new T[n];
			for (size_t i = 0; i < v.size(); i++)
			{
				vec[i] = v[i];
			}
			_start = vec;
			_finish = vec + n;
			_endOfStorage = vec + n;
		}
		vector<T>& operator= (vector<T> v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endOfStorage, v._endOfStorage);
			return *this;
		}
  • 析构:释放内存,指针置空
cpp 复制代码
		~vector()
		{
			delete[] _start;//下面两个之所以不用delete是因为他们三指向的是同一块空间 是释放一次即可
			_start = nullptr;
			_finish = nullptr;
			_endOfStorage = nullptr; //释放后立即置空是防止"重复释放野指针"的好习惯
		}
  • 赋值运算符:copy-and-swap 惯用法,深拷贝
cpp 复制代码
		vector<T>& operator= (vector<T> v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endOfStorage, v._endOfStorage);
			return *this;
		}
  • swap:交换内部指针
cpp 复制代码
		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endOfStorage, v._endOfStorage);
		}

2. 容量相关

  • size():返回元素个数
cpp 复制代码
		size_t size() const
		{
			return _finish - _start;
		}
  • capacity():返回当前已分配内存可容纳的元素个数
cpp 复制代码
size_t capacity() const
{
	return _endOfStorage - _start;
}
  • reserve:预留空间(只扩大),避免频繁扩容。使用 placement new 移动元素,正确释放旧内存
cpp 复制代码
//void reserve(size_t n)//只放大 不缩小
//{
//	if (n <= capacity()) return;
//	size_t si = size();
//	T* New_vec = new T[n];
//	memcpy(New_vec, _start, si);//这里只进行了浅拷贝 虽然开辟了新内存可以存储成员变量 ,
//	//但是一旦出现非pod数据 里面的 指针变量的指向还会是原来的空间 可能导致后面对同一块空间释放两次 导致报错
//	delete[] _start;
//	_start = New_vec;
//	_finish = New_vec + si;
//	_endOfStorage = New_vec + n;
//}
void reserve(size_t n)//只放大 不缩小
{
	if (n <= capacity()) return;
	size_t si = size();
	//T* New_vec = new T[n];
	T* New_vec = static_cast<T*>(::operator new(n * sizeof(T))); // 分配原始内存
	for (size_t i = 0; i < si; ++i) 
	{
		new (New_vec + i) T(std::move(_start[i])); // placement new 移动构造
	}
	// 销毁旧对象
	for (size_t i = 0; i < si; ++i) 
	{
		_start[i].~T();
	}
	::operator delete(_start); // 释放旧内存
	_start = New_vec;
	_finish = New_vec + si;
	_endOfStorage = New_vec + n;
}
  • resize:改变元素个数,多余元素析构,不足则构造(可指定初始值)
cpp 复制代码
void resize(size_t n, const T& value = 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()) 
		{
			reserve(n);// 扩容(会移动旧元素,更新 _start, _finish, _endOfStorage)
			//有现成的扩容直接用
		}
		// 构造新增元素
		for (size_t i = size(); i < n; ++i) 
		{
			new (_start + i) T(value);
		}
		_finish = _start + n;
	}
}

3. 元素访问

  • operator[]:下标访问,检查边界(与标准库一致)
cpp 复制代码
		T& operator[](size_t pos)
		{
			assert(pos<size());
			return *(_start + pos);
		}
		const T& operator[](size_t pos)const
		{
			assert(pos < size());
			return *(_start + pos);
		}

4. 迭代器

  • begin() / end()含const:返回普通迭代器(指针)
cpp 复制代码
typedef T* iterator;
typedef const T* const_iterator;
iterator begin()
{
	return _start;
}
const_iterator begin() const
{
	return _start;
}
iterator end()
{
	return _finish;
}
const_iterator end() const
{
	return _finish;
}
  • cbegin() / cend():返回常量迭代器
cpp 复制代码
const_iterator cbegin() const
{
	return _start;
}

const_iterator cend() const
{
	return _finish;
}

5. 修改操作

  • push_back:尾部插入,容量不足时扩容(翻倍策略)。使用循环赋值拷贝元素
cpp 复制代码
void push_back(const T& x)
{
	if (size() + 1 >= capacity())
	{
		size_t new_capacity = (capacity() == 0 ? 4 : capacity() * 2);
		reserve(new_capacity);
	}
	*(_finish) = x;
	_finish++;
}
  • pop_back:尾部删除,析构元素,大小减一
cpp 复制代码
bool empty()
{
	if (size() == 0) return true;
	return false;  //这个返回值不要忘了 条件发生与不发生
}
void pop_back()
{
	assert(!empty());
	--_finish;
	_finish->~T();
}
  • insert:在迭代器位置插入元素,后移元素,扩容处理
cpp 复制代码
iterator insert(iterator pos, const T& x)//既然给的是迭代器 那我传为下标不就可以了 
{
	if (size() + 1 >= capacity())
	{
		size_t new_capacity = (capacity() == 0 ? 4 : capacity() * 2);
		reserve(new_capacity);
	}
	size_t index = pos - _start;//如果pos是指向下标为三的元素 那么-pos之后是下标为3的位置 所以符合转化要求
	for (size_t i = size(); i > index; --i)//往前移动还是往后移动 要清楚
	{
		_start[i] = _start[i - 1];
	}
	*(_start+index) = x;//size不用管因为有人给计算出来
	_finish++;
	return _start + index;
}
  • erase:删除迭代器位置元素,前移后续元素,析构最后一个元素
cpp 复制代码
iterator erase(iterator pos)
{
	assert(!empty());
	size_t index = pos - _start;
	for (size_t i = index;i < size()-1; ++i)
	{
		_start[i] = _start[i+1];
	}
	_finish--;
	_start[size() - 1].~T();   // 析构被"删除"的元素//优化环境下平凡析构不会被调用 断点直接失效 在非优化下则会生效
	return _start + index;
}
  • swap:交换两个 vector 的内容 (已给出)

6. 辅助

  • empty:判空
cpp 复制代码
		bool empty()
		{
			if (size() == 0) return true;
			return false;  //这个返回值不要忘了 条件发生与不发生
		}

7.其中可能会出现的疑问

1.浅拷贝与深拷贝

memcpy和循环赋值之间的区别

以下我和DeepSeek的对话 可供参考

2.理解什么是pod什么是非pod

pod即内置类型 自定义类型中不包含指针作为成员变量的 如日期类

POD类型:包括基本类型(如int、char)、数组、以及只包含POD成员的struct/class。这些类型的内存表示就是数据本身,没有内部指针指向外部资源。因此,用memcpy直接逐字节复制是安全的,复制后两个对象完全独立,不会产生资源中突。

非pod则指自定义类型中包含指针作为成员变量的 如string类

非POD类型:例如string、vector以及任何管理动态内存的类。它们的内部通常包含指针成员,指向堆上分配的资源。memcpy只复制指针值(地址),而不是资源本身,导致新l日对象共享同一块资源。析构时就会重复释放,造成崩溃。

完结-----

我的gitee仓库链接

https://gitee.com/jiangmingpeng0716/c-learning-process

相关推荐
Chris _data13 小时前
C# 与 PLC Modbus RTU 通信实践:从单例到线程安全的连接监控
开发语言·安全·c#
晚烛13 小时前
CANN 分布式通信与 HCCL:多 NPU 协作的底层机制
开发语言·人工智能·分布式·python·深度学习
装不满的克莱因瓶13 小时前
新版AI开发框架SpringAIAlibaba vs AgentScope 选型指南
java·开发语言·人工智能·ai·agent·alibaba·springai
雾酩13 小时前
深拷贝与浅拷贝:一篇彻底讲明白的入门博客
开发语言·前端·javascript
丘山望岳13 小时前
C++模板特化:类型与常量的灵活掌控
c语言·开发语言·c++
阿里嘎多学长13 小时前
2026-05-24 GitHub 热点项目精选
开发语言·程序员·github·代码托管
凯瑟琳.奥古斯特13 小时前
原码与补码乘法符号位处理差异
java·开发语言·职场和发展
iiiiyu13 小时前
面向对象案例
java·大数据·开发语言·数据结构·python·编程语言
Chris _data13 小时前
C# WinForms 后台轮询 Modbus 数据与 UI 更新实践
开发语言·ui·c#