C++中vector的实现

目录

简介

特点

实现

Part 1:迭代器部分函数

Part 2 :访问权限相关的函数

Part 3 :默认成员函数

Part 4 :空间开辟相关、资源申请相关的函数


简介

C++的vector是一种序列容器,它表示可以改变大小的数组。vector在头文件<vector>中定义,是C++标准模板库(STL)的一部分。

特点

  1. 动态数组vector可以在运行时动态地扩展和收缩。
  2. 连续存储vector的元素存储在连续的内存位置,这使得通过索引访问元素非常快速。
  3. 类型安全vector是模板容器,可以存储任何类型的元素。
  4. 迭代器支持vector提供了双向迭代器,可以用于遍历容器中的元素。
  5. 容量和大小vector具有当前元素数量(大小)和最大容量两个属性。

实现

下面根据vector的特点,来完成vector的实现。在SGI STL源码文件的vector.h文件中,采用了三个迭代器成员完成了vector的实现,那我们也采用三个迭代器成员去实现vector

由于vector是模板类,因此将实现都放在了头文件中,防止出现编译链接的错误

template<class T>
class vector
{
public:
    typedef T* iterator;
    typedef const T* const_iterator;

定义好迭代器之后,就可以用迭代器去定义成员变量

成员变量

private:
    iterator _begin = nullptr;        //都是用迭代器实现的
    iterator _end = nullptr;
    iterator _end_of_storage = nullptr;

在源码中命名采用的是_start _end _end_of_storage三个成员,为了跟源码区分,我们换了命名。

在C++11中,支持类内初始化,因此我们采用了类内初始化的方式

成员函数

Part 1:迭代器部分函数

由begin()、end()系列构成


		iterator begin()
		{
			return _begin;	//保证返回类型
		}
		iterator end()
		{
			return _end;
		}
		const_iterator begin() const
		{
			return _begin;
		}
		const_iterator end() const
		{
			return _end;
		}

Part 2 :访问权限相关的函数

解引用operator[]

用来完成数据的访问,借助的是下标。应该重载好只读和读、写的双重版本

		T& operator[](size_t n)
		{
			assert(n < size());
			return _begin[n];
		}
        

        const T& operator[](size_t n) const
        {
	        assert(n < size());
	        return _begin[n];
        }

capacity函数、size函数

capacity用来观察对象的空间。由于我们采用的是迭代器实现,所以用差值表示空间

size_t capacity() const
{
	return _end_of_storage - _begin;
}

size函数用来观察size大小

size_t size() const
{
	return _end - _begin;
}

Part 3 :默认成员函数

这里主要是指构造、析构、拷贝构造、复制重载

构造函数

用来完成对象的初始化工作,应该包含1.默认构造 2.完成其他需要的构造函数

默认构造:可以是无参、全缺省、系统生成。由于我们显式定义构造函数,因此采用无参的默认构造函数。其可以采用类内初始化的方式去完成初始化工作。

	vector()		//作为无参的默认构造函数
	{}

其他构造:

官方还给出了采用迭代器去初始化和采用val去初始化的功能。

迭代器版本:

采用了模板函数,借助InputIterator去完成了初始化

template <class InputIterator>		//模板函数,参数是输入迭代器
vector(InputIterator first, InputIterator last)
{
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

指针版本的迭代器不仅可以完成解引用观察数值,还可以采用指针运算去观察元素个数

用n个val去初始化

我们先开辟出n个空间来,避免多次异地扩容导致的时间 空间消耗


		vector(size_t n, const T& val = T())	//匿名对象作为缺省对象参数
		{
			reserve(n);		//避免异地扩容

			for (size_t i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

对于传参,我们借助匿名对象传参,当对象是内置类型该怎么办呢?

注:((

        /*
        在C++中,完成了内置类型的升级,变成了对象        
        对于内置类型(如 int 或 double),T() 通常会产生一个默认初始化的值(对于 int 是 0,对于 double 是 0.0)。
        对于自定义类型,T() 将调用该类型的默认构造函数。
        */
))

拷贝构造

 我们这里采用了一种新的拷贝构造方式。注意,拷贝构造必须完成深拷贝。思路:借助swap函数去完成拷贝构造

		void swap(vector<T>& v)
		{
			std::swap(_begin, v._begin);
			std::swap(_end, v._end);
			std::swap(_end_of_storage, v._end_of_storage);
		}

     v2(v1)对于v1我们只需要内部建立好一个跟v1一样的副本。交换v2与 副本,再释放掉旧v2的空间,对于副本对象,出了作用域会自动销毁。


		// v2(v1)	
		vector(const vector<T>& v)	//形参必须是引用
			:_begin(nullptr)	//进入函数体之前,优先进行初始化
			,_end(nullptr)
			,_end_of_storage(nullptr)
		{
			vector tmp(v.begin(), v.end());

			swap(tmp);

		}	//tmp销毁,但是开辟的空间还在

赋值重载

同样借助swap函数,完成赋值重载。赋值重载必须也是深拷贝。

	//v2 = v1
	vector<T>& operator=(vector<T> tmp)		//引用返回
	{
		swap(tmp);

		return *this;
	}

析构函数

在C++中,析构空指针通常不会导致严重的问题。析构函数的主要作用是释放对象所占用的资源,例如动态分配的内存。如果一个指针是空的(即它不指向任何对象),调用其析构函数通常不会执行任何操作,因为没有任何资源需要释放

		~vector()
		{
			if (_begin)
				delete[] _begin;
			_begin = _end = _end_of_storage = nullptr;
		}

Part 4 :空间开辟相关、资源申请相关的函数

reserve

用来预开辟空间。修改capacity的大小,一般来说,只能增大空间,不能变小。

reserve函数的几个步骤:1.预开辟空间 2.空间内容的转移(深拷贝) 3.释放就空间 4.更新成员变量

我们建立了一个变量sz来接收size函数的返回值。当使用size函数的时候,由于size函数内部使用迭代器指针完成的,为了防止指针运算带来的弊端,我们采用sz来接受size的大小。


			//1.开空间 2.深拷贝 3.清除原来的空间 4.修正
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n];	//开空间
				size_t sz = size();		//需要用函数尽量提前建立好返回变量

				if (_begin != nullptr)		//解引用需要保证不为空
				{
					for (size_t i = 0; i < sz; ++i)	//深拷贝,用赋值重载,不用memcpy(内部浅拷贝)
					{
						tmp[i] = _begin[i];
					}

					delete[] _begin;	//释放旧空间
				}
				_begin = tmp;
				_end = _begin + sz;
				_end_of_storage = _begin + n;

			}
		}

需要注意的是,内部不可以用memcpy去拷贝内容。memcpy内部是浅拷贝,虽然开出了空间,但是指针还是没有指向新空间。

resize

同样可以进行空间的预开辟,也可以完成初始化操作。

当预开辟的大小n,小于size时,只需要修正即可

当大于size时,可以进行reserve(n)操作,把剩余的内容进行缺省值填充

void resize(size_t n, const T& val = T())
{
	size_t sz = size();
	if (n <= sz)
	{
		_end = _begin + n;
	}
	else
	{
		reserve(n);

		while (_end < _begin + n)
		{
			*_end = val;
			++_end;
		}
	}

}

push_back

用来完成尾插,这是最重要的插入方式。push_back可以自动调整空间。

步骤:1.判满 (开空间) 2.尾插 3._end++

void push_back(const T& x)
{
	//if (_end == _end_of_storage)	//尽量去使用成员变量
	//{
	//	size_t newcp = (size() == 0) ? 4 : 2 * capacity();
	//	reserve(newcp);
	//}

	//*_end = x;
	//++_end;

	insert(_end, x);
}

insert函数

用来完成数据的任意位置的插入。像如挪动数据的函数,消耗较大,尽量不要用。首先要assert保证插入的位置正确。

1.判满 2.挪动数据 3._end++


		void insert(iterator pos, const T& x)
		{
			assert(pos <= _end && pos >= _begin);
			//判满
			if (_end == _end_of_storage)
			{
				size_t len = pos - _begin;
				size_t newcp = (size() == 0) ? 4 : 2 * capacity();
				reserve(newcp);		//reserve之后,更新迭代器pos
				pos = _begin + len;
			}

			iterator end = _end - 1;	//_end不存储任何元素

			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;    //插入数据
			++_end;
		}

使用reserve之后,迭代器失效,需要更新迭代器pos。因此使用insert、erase之后,迭代器需要更新使用。

erase

用来完成数据的删除。在删除的时候,必须能够保证头删。

1.挪动数据 2.--_end;


		iterator erase(iterator pos)
		{
			assert(pos >= _begin && pos < _end);
				
			iterator it = pos + 1;	//保证可以完成头删的下标(+1即可)
	
			while (it < _end)
			{
				*(it - 1) = *it;
				++it;
			}

			--_end;

			return pos;

		}

  • 16
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值