C++初阶:vector的使用与自实现

1. vector与顺序表

  1. 我们在初阶数据结构中学习了简单数据结构链表,这一数据结构的要求为底层物理空间连续,且支持头插尾插等插入操作,头删尾删等删除操作。在C++的STL中也有对这一简单数据结构的实现,将其模板化加入库中,不仅提高了泛用性还提供了便捷的接口,接下来,就让我们对这一容器vector进行掌握学习。

2. vector的使用

2.1 默认成员函数

1. 构造与拷贝构造

//默认构造,内容为空
vector<int> v1;

//指定数据内容与个数构造
vector<int> v2(3,100);

//迭代器区间构造
vector<int> v3(v2.begin(), v2.end());

//使用列表构造
vector<int> v4({1,2,3,4,5});

//拷贝构造
vector<int> v5(v4);

2. operator=赋值运算符重载

vector<int> v1(3, 100);
vector<int> v2;
//给已经存在的对象赋值
v2 = v1;

//当使用赋值运算符对还没有产生的对象赋值时,会调用拷贝构造
vector<int> v3 = v1;

2.2 vector的迭代器

//正向反向迭代器
vector<int> v1;
vector<int>::iterator it1 = v1.begin();
vector<int>::iterator it2 = v2.end();

vector<int>::reverse_iterator it3 = v3.rbegin();
vector<int>::reverse_iterator it4 = v4.rend();

//const修饰的正反向迭代器
vector<int>::const_iterator it5 = v5.cbegin();
vector<int>::const_iterator it6 = v6.cend();

vector<int>::const_reverse_iterator it7 = v7.crbegin();
vector<int>::const_reverse_iterator it7 = v8.crend();

2.3 容量相关成员函数

1. 容量大小

vector<int> v1(3, 100);

//当前存储的数据个数
cout << v1.size() << endl;

//理论上最多能够存储的数据个数,无参考意义
cout << v1.max_size() << endl;

//当前vector的容量
cout << v1.capacity() << endl;

//检测当前vector是否为空,无存储元素
cout << v1.empty() << endl;

2. 扩容与缩容

vector<int> v2(3,100);

//将当前vector扩容至指定大小,1.5被扩容
v2.reserve(10);

//将当前vector扩容至指定大小,再将扩容出的部分用指定数填满
//vector长度跟随增长,未指定时,默认使用0填充
v2.resize(20);

//缩容,将当前大于有效数据长度的部分缩减
//将缩小当前数据的长度
v2.shrink_to_fit();

2.4 遍历访问方式

vector<int> v1(5,100);

//operator[]运算符重载
//报警告
v1[0];

//抛异常
v1.at[1];

//返回vector的首尾元素
v1.front();
v.back();

2.5 vector的修改操作

1. 插入,赋值操作

vector<int> v1(5, 100);
vector<int> v2;
//assign
//指定用多少个什么数赋值
v2.assgin(4,100);
//使用迭代器区间赋值
v2.assgin(v1.begin(), v2.end());

//尾插
v2.push_back(10);

//随机插入
//在指定位置(迭代器表示),插入一个数
v2.insert(v2.begin(), 10);

//在指定迭代器位置,插入n个数
v2.insert(v2.begin(), 3, 10);

//在指定迭代器位置插入一段迭代器区间
v2.insert(v2.begin(), v1.begin(), v1.end());
  1. 补充:头插的效率很低,且可以通过随机插入代为实现头插,所以vector没有去实现头插

2. 删除操作

vector<int> v1(10, 100);

//尾删
v1.pop_back();

//删除指定迭代器位置的数据
v1.erase(v1.begin());

//删除指定迭代器区间的数据
v1.erase(v1.begin(), v1.begin() + 3);

//清空vector
v1.clear();

//补充:成员函数swap
vector<int> v2(10, 9);

v1.swap(v2);
  1. 补充:STL为开源项目,查看源代码的方式
    <1> 不要进行一行一行的细节查看
    <2> 查看整体框架
    <3> 成员变量,构造函数,插入操作作为突破口

3. vector的自实现

3.1 自实现功能

  1. 默认成员函数:构造,拷贝构造,析构,operator=赋值运算符重载
  2. 遍历访问方式:iterator,operator[],front,back
  3. 容量相关成员函数:capacity,size,reserve,resize
  4. 插入删除操作:push_back,pop_back,insert,erase
  5. 补充:swap

3.2 vector的结构与自实现的逻辑

  1. 不再单独创建成员变量来记录容量与数据长度,采用三个迭代器的方式来进行信息的维护

在这里插入图片描述

  1. 我们可以通过合适的实现逻辑与复用完成对vector接口的实现

在这里插入图片描述

3.3 自实现vector功能接口

1. 成员变量与迭代器

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

	//迭代器
	iterator begin()
	{
		return _start;
	}

	iterator begin() const
	{
		return _start;
	}

	iterator end()
	{
		return _finish;
	}

	iterator end() const
	{
		return _finish;
	}

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

2. 容量相关

size_t size()
{
	return _finish - _start;
}

size_t capacity()
{
	return _endofstorage - _start;
}

3. 扩容与插入删除

  1. reserve
void reserve(size_t n)
{
	if (n > capacity())
	{
		//开辟空间
		T* tmp = new T[n];
		int old = _finish - _start;

		//检测是否需要赋值
		if (_start)
		{
			for (int i = 0; i < old; i++)
			{
				tmp[i] = _start[i];
			}

			//释放旧空间
			delete[] _start;
		}
		
		//直接调用capacity与size()会导致报错
		//扩容后指针失效
		_start = tmp;
		_finish = _start + old;
		_endofstorage = _start + n;
	}
}

在这里插入图片描述

  1. push_back
void push_back(const T& val)
{
	if (_finish == _endofstorage)
	{
		int newcapacity = capacity() == 0 ? 4 : 2 * capacity();
		reserve(newcapacity);
	}

	*_finish = val;
	_finish++;
}
  1. resize
//C++中为内置类型也添加了构造
//double 0.0,char '\0',int 0
void resize(size_t n, T val = T())
{
	if(n > size())
	{
		//扩容
		reserve(n);
		//填值
		while(_finish < _start + n)
		{
			*_finish = val;
			++_finish;
		}
	}
	else
	{
		//缩容
		_finish = _start + n;
	}
}
  1. pop_back
void pop_back()
{
	assert(size() > 0);

	--_finish;
}

4. 默认成员函数

  1. 无参构造与拷贝构造
//构造
vector()
{}

//拷贝构造
vector(const vector<T>& v)
{
	auto it = v.begin();
	//范围for
	while (it < v.end())
	{
		push_back(*it);
		it++;
	}
}
  1. swap与赋值重载
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;
}
  1. 迭代器区间构造
    <1> 迭代器区间构造,支持使用其他容器初始化
    <2> string,list,数组(数组的原生指针是天然的迭代器)
template<class Inputiterator>
vector(Inputiterator it1, Inputiterator it2)
{
	//其他数据结构的内存空间可能不连续
	while (it1 != it2)
	{
		push_back(*it1);
		it1++;
	}
}
  1. 用指定的n个数进行构造
    <1> 当参数为两个int时,迭代器区间构造的调用优先级会比参数类型为size_t, T的构造函数高
    <2> 重载如下第二个构造函数就可以避免上述情况的发生
//n个指定值构造
vector(size_t n, T val = T())
{
	//直接复用resize
	resize(n, val);
}

//两个参数都为int时,保证调用此构造
vector(int n, T val = T())
{
	resize(n, val);
}
  1. 析构函数
~vector()
{
	if (_start)
	{
		delete[] _start;
	}

	_start = _finish = _endofstorage = nullptr;
}

5. 迭代器失效,深浅拷贝与erase,insert

  1. insert(向指定迭代器位置插入一个元素)
//在pos位置插入一个数
iterator insert(iterator pos, T val)
{
	assert(pos >= _start && pos <= _finish);

	int old = pos - _start;
	//扩容后迭代器失效
	if (_finish == _endofstorage)
	{
		int newcapacity = capacity() == 0 ? 4 : 2 * capacity();
		reserve(newcapacity);
	}

	//避免自定义类型浅拷贝,不能使用memmove等函数
	//向后依次挪移,使用存储数据类型的赋值重载
	//迭代器插入没有下标插入size_t类型的特殊处理
	pos = _start + old;
	auto end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end ;
		end--;
	}

	*pos = val;
	_finish++;

	//防止迭代器失效,可以使用插入与++分离的方式
	//不能保证不扩容
	return pos;
}

扩容导致的迭代器失效:
在这里插入图片描述

  1. 补充1:使用memove只会进行浅拷贝,当存储数据是自定义类型时,进行赋值时,不会去析构旧空间,会导致空间泄漏。
  1. erase(删除指定迭代器位置元素)
iterator erase(iterator pos)
{
	assert(pos >= _start && pos < _finish);

	//迭代器失效,memmove浅拷贝
	auto it = pos + 1;
	while (it < _finish)
	{
		*(it - 1) = *it;
		it++;
	}

	_finish--;

	return pos;
}
  1. 补充2:迭代器失效
    <1> 不同平台对insert与erase的实现方式不同,可能会进行空间的扩容或缩容,这样就会导致原本迭代器的失效。
    <2> 删除或插入后,原本迭代器指向元素的相对位置改变,插入删除操作与++操作分离。
  1. 补充3:string与vector自实现方式
    <1> string的存储元素只是字符,所以没有深拷贝,可以直接进行strcpy系列的函数使用
    <2> vector的存储数据可能是自定义类型,必须要进行深拷贝
    <3> string挪动数据使用的是下标的方式,因为size_t的下标类型所以要考虑头插的特殊情况处理
    <4> vector使用迭代器的方式实现数据挪动的操作,避免的string的类似问题
    <5>string的成员变量与对数据的维护方式没有使用迭代器
    <6> vector使用迭代器维护数据,进行数据操作,也因此导致迭代器失效这一情况的出现

6. operator[]的运算符重载

vector<T>& operator[](size_t pos)
{
	assert(pos < _finish - _start);

	return _start + pos;
}

const vector<T>& operator[](size_t pos) const
{
	assert(pos < _finish - _start);

	return _start + pos;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值