C++初阶---vector的使用及模拟实现

vector介绍

template < class T, class Alloc = allocator<T> > class vector; 
// generic template

vector是表示可以改变大小的数组的序列容器
就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理
本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小
vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的
vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长
与其它动态序列容器相比(deques, lists and forward_lists), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起lists和forward_lists统一的迭代器和引用更好

——————————————————————————翻译自:vector -C++ Reference


vector使用

详细请参考:cplusplus -vector

①vector构造函数

1.构造一个空容器,没有元素

explicit vector (const allocator_type& alloc = allocator_type());

2.构造一个包含 n 个元素的容器。每个元素都是 val 的副本

explicit vector (size_type n, const value_type& val = value_type(),
                const allocator_type& alloc = allocator_type());

3.构造一个包含与范围 [first,last) 一样多的元素的容器,每个元素都由该范围内的对应元素以相同的顺序构造

template <class InputIterator>
        vector (InputIterator first, InputIterator last,
                const allocator_type& alloc = allocator_type());

4.以相同的顺序构造一个容器,其中包含 x 中每个元素的副本

vector (const vector& x);

示例代码:

// constructing vectors
#include <iostream>
#include <vector>
int main ()
{
   //四种构造方式
	std::vector<int> first;// int类型的空vector
	std::vector<int> second (4,100);// 四个整数,值为 100
	std::vector<int> third (second.begin(),second.end());
	// 迭代second
	std::vector<int> fourth (third);// third的拷贝
	
	// 迭代器构造函数也可用于从数组构造:
	int myints[] = {16,2,77,29};
	std::vector<int> fifth (myints, myints + sizeof(myints) / sizeof(int) );
	std::cout << "The contents of fifth are:";
	for (std::vector<int>::iterator it = fifth.begin(); it != fifth.end(); ++it)
		std::cout << ' ' << *it;
	std::cout << '\n';
	return 0;
}

输出:
The contents of fifth are: 16 2 77 29

②vector的访问及遍历操作

vector和string相似,迭代器也是指针具体参考C++初阶—STL入门+(string)的iterator部分
注意

  1. rbegin和rend就不是原生指针
  2. 指向数组的指针是天然的迭代器
名称功能
begin迭代器返回到开头
end迭代器返回到结尾
cbeginconst重载
cendconst重载
rbegin反向迭代器返回到开头
rend反向迭代器返回到结尾
crbeginconst重载
crendconst重载

③vector常见容量操作

名称功能
size返回大小
max_size(用处不大)
resize类似string的resize,只是char缺省值是‘\0’,int缺省值为‘0’
reserve类似string的reserve
capacity容量
empty判空

③vector访问操作

注意

  1. operator[]会检查下标是否小于size,越界大于就assert终止程序报错,而at是抛异常

  2. reserve后未初始化的空间不能访问,resize默认会初始化,可以访问

  3. 迭代器区间都是左闭右开

名称功能
operator[]取得元素
at取得元素
front取首元素
back取尾元素

④vector修改操作

vector没有find成员函数
find函数模板是std下的
实现:

template<class InputIterator, class T>
 	InputIterator find (InputIterator first, InputIterator last, const T& val)
{
 while (first!=last) {
   if (*first==val) return first;
   ++first;
 }
 return last;
}

返回迭代区间 [first,last)的第一个值为val的元素(指相当于指针)

名称功能
assign分配vector内容
push_back尾插
pop_back尾删
insert插入元素
erase删除元素
insert插入元素
swap交换元素

assign:

  1. template <class InputIterator>
    void assign (InputIterator first, InputIterator last);
  2. void assign (size_type n, const value_type& val);
std::vector<int> first;
std::vector<int> second;
std::vector<int> third;
first.assign (7,100);             // 7个值为100的整形

std::vector<int>::iterator it;
it=first.begin()+1;

second.assign (it,first.end()-1); // 5个first中间的元素

int myints[] = {1776,7,4};
third.assign (myints,myints+3);   // 从数组assign

std::cout << "Size of first: " << int (first.size()) << '\n';
std::cout << "Size of second: " << int (second.size()) << '\n';
std::cout << "Size of third: " << int (third.size()) << '\n';

输出:
Size of first: 7
Size of second: 5
Size of third: 3


vector模拟实现

结构:

C++语言使用的标准模板库v30版本,作为一份源码来说易于剖析和学习-Standard Template Library v30 uses language version C++
我们找到stl_vector.h,会看到被protect修饰的三个迭代器成员变量,结合之前string在定义的_str,_size,_capacity,
这里推测应该是指向首元素,末尾元素,容量位置的三个指针,iterator是typedef T* iterator出来的,也就是指针

在这里插入图片描述
在这里插入图片描述
下面.h中实现的构造函数印证了我们的想法
在这里插入图片描述

成员函数实现

由于有之前的string模拟实现,我们只在这里实现个别函数

1.构造
默认构造
vector()
:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{}
构造一个包含 n 个元素的容器。每个元素都是 val 的副本
vector(int n, const T& value = T())
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	reserve(n);
	while (n--)
	{
		push_back(value);
	}
}
迭代器区间构造
template<class InputIterator>
	vector(InputIterator first, InputIterator last)
{

	while (first != last)
	{
		push_back(*first);
		first++;
	}
}
拷贝构造
vector(const vector<T>& v)
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	reserve(v.capacity());
	for ( const auto& e : v)
	//注意这里要用引用,如果不是引用,相当于传值和传址的问题,如果是自定义类型,传值拷贝会消耗大量空间
	{
		push_back(e);
	}
	//vector<T> tmp(v.begin(), v.end());
	this->swap(tmp);
	//swap(tmp);
}

注意 范围for要用引用,如果不是引用,相当于传值和传址的问题,如果是自定义类型,传值拷贝会消耗大量空间
由于复用迭代器区间的方法不能提前开好空间,所以传统方法相对高效

2.赋值运算符重载
void swap(vector<T>& v)
{
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_end_of_storage, v._end_of_storage);
}
vector<T>& operator= (vector<T> v)
{
	swap(v);
	return *this;
}
3.容量
reserve(+memcpy浅拷贝)

注意:_finish = _start + marksize; mark_size要提前保存不然会出错

void reserve(size_t n)
{
	if (n > this->capacity())
	{

		T*tmp = new T[n];
		int marksize = this->size();
		memcpy(tmp, _start, sizeof(T)*marksize);//注意memcpy的第三个参数是总大小不是元素个数
		delete[] _start;
		//注意memcpy仅适用内置类型,有资源分配的自定义类型会导致内存泄露
		_start = tmp;
		_finish = _start + marksize;
		_end_of_storage = _start + n;
	}
}

memcpy分析
在这里插入图片描述

  1. memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中
  2. 如果拷贝的是内置类型的元素,memcpy即高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝

正确的应该这样写:

void reserve(size_t n)
{
	if (n > this->capacity())
	{

		T* tmp = new T[n];
		int marksize = this->size();
		//memcpy(tmp, _start, sizeof(T) * marksize);
		//注意memcpy的第三个参数是总大小不是元素个数
		注意memcpy仅适用内置类型,有资源分配的自定义类型会导致内存泄露
		if (_start)
		{
			for (int i = 0; i < marksize; i++)
			{
				tmp[i] = _start[i];// 如果T是string -> 
				// 调用的就是string的operator=(深拷贝)
			}
			delete[] _start;
		}
		_start = tmp;
		_finish = _start + marksize;
		_end_of_storage = _start + n;
	}
}
resize
void resize(size_t n, const T& value = T())
{
	if (n <= size())
	{
		_finish = _start + n;
	}
	else
	{
		if (n > capacity())
		{
			reserve(n);
		}
		int times = n - size();
		while (times--)
		{
			push_back(value);
			_finish++;
		}
	}
}
4.修改操作

先参考下面的 迭代器失效

insert

注意 如果在扩容时不更新pos指针就出现下面的情况
这里是引用

iterator insert(iterator pos, const T& x)
{
	assert(pos >= _start && pos <= _finish);//注意断言
	if (size() == capacity())
	{
		size_t len = pos - _start;//增容后pos应该指向新的空间,这里要记录下位置
		size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		//第一次capacity为0要开四个元素的空间
		reserve(newcapacity);
		pos = _start + len;//pos指针更新
	}
	vector<int>::iterator it = end();
	while (it >= pos)
	{
		*it = *(it - 1);
		it--;
	}
	*pos = x;
	_finish++;
	return pos;
}
erase
iterator erase(iterator pos)
{
	assert(pos >= _start && pos < _finish);

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

vector迭代器失效问题

在vector类函数中有insert和erase ,我们知道,对于string和vector来说迭代器是原生指针

VS下

insert
  1. insert时需要扩容
    在reserve函数中,扩容是新开辟一块空间,把原来的数据拷贝到新空间上
    在这里插入图片描述
    此时pos指针是野指针,在之前我们已经说过,编译器对越界访问是抽查,但在vs下直接报错
    在这里插入图片描述
    在这里插入图片描述

  1. insert时不需要扩容
    我们提前开辟好空间保证插入时不扩容
    pos正常
    在这里插入图片描述
    监视窗口中显示一切正常,但是他的意义已经变了,也属于迭代器失效,pos不再指向原来的值
    测试情况编译器仍然报错
erase
  1. erase同样,但是这里只要不是删的最后一个元素就没有野指针问题,VS会进行强制检查
    在这里插入图片描述

Linux下

相同的代码Linux都没有报错
下面是erase的测试
在这里插入图片描述
这里是引用


危害

VS只要是迭代器失效就会强制检查这里讨论很多情况都不检查的Linux
删除v中所有偶数

std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
//v.push_back(5);
//要求删除v中所有偶数
std::vector<int>::iterator it = v.begin();
while (it != v.end())
{
	if (*it % 2 == 0)
	{
		v.erase(it);
	}
	++it;
}

在这里插入图片描述
在这里插入图片描述
Segmentation fault (core dumped)多为内存不当操作造成。空指针、野指针的读写操作,数组越界访问,破坏常量等

库中的insert,erase实现

insert 返回值指向第一个新插入元素的迭代器

iterator insert (iterator position, const value_type& val);

erase 指向被函数调用擦除的最后一个元素之后的元素的新位置的迭代器

iterator erase (iterator position);

上面的代码改为:

std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(2);
v.push_back(3);
v.push_back(4);
std::vector<int>::iterator it = v.begin();
while (it != v.end())
{
	if (*it % 2 == 0)
	{
		// erase 返回删除数据的下一个数据的位置
		it = v.erase(it);
	}
	else
	{
		++it;
	}
}

在这里插入图片描述
正常实现功能

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值