STL——vector

STL历史

STL 的主要实现版本

  1. HP STL:由 Alexander Stepanov 和 Meng Lee 在惠普 Palo Alto 实验室工作时完成,是 C++ STL 的第一个实现版本。

  2. SGI STL:由 Silicon Graphics Computer Systems 公司参照 HP STL 实现,被 Linux 的 C++ 编译器 GCC 所采用。

  3. STLport:由 Boris Fomitchev 开发的跨平台版本,基于 SGI STL。

  4. P.J.Plauger STL:由 P.J.Plauger 实现,被 Visual C++ 编译器所采用。

  5. Rouge Wave STL:由 Rouge Wave 公司开发,用于 Borland C++ 编译器。

STL六大组件

  1. 容器(Containers):如 vector, list, deque, set, map,用于存放数据。

  2. 算法(Algorithms):如 sort, search, copy, erase。

  3. 迭代器(Iterators):用于连接容器和算法。

  4. 仿函数(Functors):行为类似函数,可作为算法的策略。

  5. 配接器(Adapters):用于修饰容器、仿函数或迭代器接口。

  6. 配置器(Allocators):负责空间配置与管理。

Vector

1、vector 是表示可变大小数组的容器,一般是异地扩容(需要拷贝操作)

2、vector 采用连续空间存储,可采用下标随机访问

3、vector 的尾插尾删相对高效,但在其他位置的增删操作需要挪动元素,效率较低

模拟实现

构造函数

vector 以迭代器作为成员变量,且此处迭代器的实现为 原生指针

template<class T>
class myVector
{
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;
	}

	myVector()
		:_start(nullptr)
		,_finish(nullptr)
		,_end_of_storge(nullptr)
	{}

private:
	iterator _start;
	iterator _finish;
	iterator _end_of_storge;
};

增 -- insert

void insert (iterator position, size_type n, const T& val);

向 position 位置插入 n 个 val

首先判断 position 的合法性(注意 assert 在 release 版本代码中不起作用,后面需要使用捕获异常),其次检查空间是否足够,如果不够就按照 2 倍来开足够的空间,最后挪动元素,并插入 n 个 val

	size_t capacity()
	{
		return _end_of_storge - _start;
	}
    size_t size()
	{
		return _finish - _start;
	}

    void insert(iterator position, size_t n, const T& val)
	{
		assert(position >= _start && position <= _finish);
		if (_finish + n > _end_of_storge)
		{

			size_t new_capacity = capacity() == 0 ? 6 : capacity * 2;
			reserve(new_capacity);

		}
	}

如果空间不够,扩容至 new_capacity,实现扩容函数 reserve

reserve

reserve 中在开好空间后,需要实现深拷贝,那么使用 T 类型的赋值重载 解决两个已经存在的对象间的深拷贝问题;至于 T 类型的赋值重载怎么实现的,那是 T 类的事情,不关我 myVector 的事(当然此处默认 T 类有默认构造、赋值重载)

实际上,如果 T 没有实现深拷贝的赋值重载就 g 了;

iterator src = _finish;
while (src != position) {
    --src;
    new (src + n) T(*src);   // 拷贝构造到目标地址
    (src)->~T();             // 析构原位置对象(可选)
}

所以其实使用 placement new 定位 new 来拷贝构造未初始化的内存是更完美的方法

	void reserve(size_t n)
	{
		if (n > capacity())	// 需要扩容的容量 大于 实际容量时才执行扩容
		{
			size_t old_size = size();

			T* tmp_start = new T[n];
			if (size() > 0)	// 有内容才需拷贝,且需要实现深拷贝
			{
				for (size_t i = 0; i < size(); i++)	// 循环次数即元素个数
				{
					*(tmp_start + i) = *(_start + i);	// 调 T 类型的赋值重载处理深拷贝
				}
			}
			delete[] _start;
			_start = tmp_start;
			_finish = _start + old_size;
			_end_of_storge = _start + n;
		}
	}

回到 insert,扩完空间后,从 position 向后挪动 n 个位置,并插入 n 个 val

	void insert(iterator position, size_t n, const T& val)
	{
		assert(position >= _start && position <= _finish);
		if (_finish + n > _end_of_storge)	// 扩容
		{
			size_t new_capacity = capacity() == 0 ? 6 : capacity() * 2;
			reserve(new_capacity);
		}
		iterator begin = _finish;	// 从后向前挪动
		while (begin > position)
		{
			begin--;
			*(begin + n) = *begin;
		}

		for (size_t i = 0; i < n; i++)	// 插入 n 个 val
		{
			*(position + i) = val;
		}
		_finish += n;
	}
bug 出现:异地扩容后迭代器的失效

在经过下面这段代码之前,position 指向原空间的某位置;如果发生了异地扩容,但是 position 没有跟着改变,那么就变成了悬空迭代器 or 野指针

        if (_finish + n > _end_of_storge)	// 扩容
		{
			size_t new_capacity = capacity() == 0 ? 6 : capacity * 2;
			reserve(new_capacity);
		}

所以我们需要在扩容前,记录下 position 的偏移量,在扩容后更新

	void insert(iterator position, size_t n, const T& val)
	{
		assert(position >= _start && position <= _finish);
		if (_finish + n > _end_of_storge)	// 扩容
		{
			size_t pos_offset = position - _start;	// 记录 position 偏移量
			size_t new_capacity = capacity() == 0 ? 6 : capacity() * 2;
			reserve(new_capacity);
			position = _start + pos_offset;	// 更新 position
		}
		iterator begin = _finish;	// 从后向前挪动
		while (begin > position)
		{
			begin--;
			*(begin + n) = *begin;
		}

		for (size_t i = 0; i < n; i++)	// 插入 n 个 val
		{
			*(position + i) = val;    // T 类的赋值重载
		}
		_finish += n;
	}

包一丝,下面这段 定位 new 的 代码应该是 挪动数据时,对 _finish 后未初始化的内存,拷贝时用的

iterator src = _finish;
while (src != position) {
    --src;
    new (src + n) T(*src);   // 拷贝构造到目标地址
    (src)->~T();             // 析构原位置对象(可选)
}

增 -- push_back

void push_back (const T& val);

复用 insert

	void push_back(const T& val)
	{
		insert(_finish, 1, val);
	}

删 -- erase

iterator erase (iterator position);

删掉 position 位置的元素,返回被删掉元素后那个元素的迭代器

这里是实现删掉一个元素,标准库还提供了重载是删除迭代器区间的元素;整体从前向后挪动元素覆盖掉要删除的元素即可

	iterator erase(iterator position)
	{
		assert(position >= _start && position <= _finish);
		iterator begin = position + 1;
		while (begin < _finish)
		{
			*(begin - 1) = *begin;
			begin++;
		}
		_finish--;
		return position;
	}

删 -- pop_back

void pop_back();

复用 erase

	void pop_back()
	{
		erase(_finish - 1);
	}

operator[ ]

重载 [ ] 运算符

	T& operator[](size_t i)
	{
		return *(_start + i);
	}

拷贝构造

拷贝构造也是构造,也走一遍初始化列表

这里在 拷贝 数据时,可以复用 push_back,因为 push_back 中实现了深拷贝逻辑;

但是如果不复用,可以直接使用 定位 new 来对申请好未初始化的内存 拷贝构造,最后处理 _finish 和 _end_of_storge,也是比较完美的方法;vector 里装 内置类型 和 自定义类型都可以解决

	myVector(const myVector<T>& v)
		:_start(nullptr)
		,_finish(nullptr)
		,_end_of_storge(nullptr)
	{
		this->reserve(v.capacity());	// 为 this 分配 和 v 一样大的原始内存
		for (size_t i = 0; i < v.size(); i++)
		{
			//push_back(v[i]);
			new (_start + i) T(v[i]);	// 使用定位 new 来拷贝构造
		}
		_finish = _start + v.size();
		_end_of_storge = _start + v.capacity();
	}

析构函数

	~myVector()
	{
		delete[] _start;
		_start = _finish = _end_of_storge = nullptr;
	}

resize

void resize (size_t n, T val = T());

resize 的功能分段考虑,如果 n <= this->size( ) ,那么将 size( ) 减小至 n;如果 n > this->size( ),那就将 size( ) 扩大到 n,被扩充的元素值设置为 val 

	void resize(size_t n, T val = T())
	{
		if (n <= size())	// 删掉 size() - n 个元素
		{
			_finish = _start + n;
		}
		else
		{
			reserve(n);	// 如果 n < capacity() reserve 什么也不会做
			while (size() < n)
			{
				// *_finish = val;
				new (_finish) T(val);
				_finish++;
			}
		}
	}

考虑一个问题:如果 T 类型中申请了资源,而我们在 n <= this->size( ) 又没有释放,不就内存泄露了?所以在删掉元素时,应当显式调用析构函数清理;那么如果 T 是内置类型,调用析构函数的操作会被编译器优化成 空操作的

	void resize(size_t n, T val = T())
	{
		if (n <= size())	// 删掉 size() - n 个元素
		{
			// _finish = _start + n;
			while (_start + n != size())
			{
				_finish--;
				_finish->~T();	// 显式调用 T 析构函数,适配资源清理情况
			}
		}
		else
		{
			reserve(n);	// 如果 n < capacity() reserve 什么也不会做
			while (size() < n)
			{
				// *_finish = val;
				new (_finish) T(val);
				_finish++;
			}
		}
	}

小结

容器中存储的可能是 内置类型 或 自定义类型 或 有申请内存资源的自定义类型,在实现 容器内元素 的拷贝逻辑时,用 placement new 是比较标准的做法,它会调用该类型的 拷贝构造 完成对所申请原始内存的初始化。

不过没有使用 try catch 来捕获可能的异常。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值