vector

vector的常用接口

vector的构造

vector()                                        无参构造

vector(size_type n, const value_type& val = value_type())          构造并初始化n个val

vector (const vector& x)                拷贝构造

vector (InputIterator first, InputIterator last)                         使用迭代器构造

vector iterator的使用 

begin() + end()                                 获取第一个元素的位置和最后一个元素的下一个位置

rbegin() + rend()                               获取最后一个元素的位置和第一个元素的前一个位置

vector容量操作接口

size()                                                获取元素个数

capacity()                                         获取容量大小

empty()                                             判断是否为空

resize(n, val)                                    改变vector的size

reserve                                             改变vector的capacity

注意:使用resize接口时,val是一个缺省的,默认给vector元素的初始值。当改变的size小于原本的size,那么直接截断多余的部分;当改变的size大于原本的size,那么多余的部分用val的值填充;当改变的size大于原本的capacity,那么会对原本的vector进行扩容操作。具体文档如下:

reseve开辟空间在vs下capacity是按照1.5倍进行增长的,g++是按照2倍进行增长的。2倍增长时间效率更优,但可能会造成空间上的浪费(相对于1.5倍增长)。不需要纠结具体多少倍比较合适,按照实际的需求去定义增长的倍数。

vector的增删改查

push_back                                       尾插

pop_back                                         尾删

find                               查找(注意这是algorithm库中的函数模块,并不是vetor的成员函数)

inser                                              在pos位置插入val,后面的元素后移(注意pos是迭代器)

erase                                              删除pos位置的元素,后面的元素前移

swap                                               交换两个vector的元素空间

operator[]                                         让vector像数组一样访问

vector迭代器失效问题

1、会引起底层空间改变的操作,都可能引起迭代器失效,比如:resize、reserve、insert、assign、push_back等。

#include<iostream>
#include<vector>
using namespace std;
int main()
{
	vector<int>v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3); 
	v1.push_back(4);
	v1.push_back(5);
	vector<int>::iterator it = v1.begin();
	v1.push_back(6);
	//v1.push_back(7);

	while (it != v1.end())
	{
		cout << *it << ' ';
		it++;
	}
	cout << endl;
	return 0;
}

上面这个代码注释尾插7就可以运行。插入操作是需要扩容的,扩容是新开辟一块大的空间,然后把旧的空间复制给新的空间,然后释放旧的空间。当插入7的时候,需要扩容,那么这个it指向释放了的空间。所以使用迭代器需要在可能对容量操作的代码之后

2、指定位置元素的删除操作(erase)

#include<iostream>
#include<vector>
using namespace std;
int main()
{
	vector<int>v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3); 
	v1.push_back(4);
	v1.push_back(5);
	v1.push_back(6);
	vector<int>::iterator it = v1.begin();
	while (it != v1.end())
	{
		if (*it % 2 == 0)
			v1.erase(it); // it = v1.erase(it); 对迭代器重新赋值就可以了。
		else
			it++;
	}
	for (int e : v1)
		cout << e << ' ';
	cout << endl;
	return 0;
}

对于上面代码是实现vector中偶数的删除。当it指向的元素是偶数,那么就会删除it指向的元素,然后后面的元素往前移。假如没有指向偶数那么it++。并没有改变底层的空间,但是还是会报错。这是因为当it指向最后一个位置是偶数,那么需要删除,后面元素前移,这个时候it就是指向end,那么it就失效了。所以,vector删除任意的位置,vs就认为该位置的迭代器失效了。

3、但是,在Linux下,g++编译器对迭代器失效并没有这么严格

对于第一种迭代器失效的情况,g++还是可以运行,但是结果是错误的。

对于第二种迭代器失效的情况,当最后一个元素没有被删除,那么就可以运行,因为当删除一个位置的元素,后面的元素会前移去填充这个删除的位置,那么这个位置在g++下认为还是可以访问的。如果最后一个元素被删了,那么就不能运行了。

总得来说:对迭代器失效得解决办法就是对迭代器重新赋值

vector的模拟实现

成员变量的定义

template<class T>
class vector
{
	typedef T* iterator;
	typedef const T* const_iterator;
private:
	iterator _start;
	iterator _finish;
	iterator _end_of_storage;
};

构造函数和析构函数

vector()
	:_start(nullptr)
	,_finish(nullptr)
	,_end_of_storage(nullptr)
{}
~vector()
{
	delete[] _start;
	_start = _finish = _end_of_storage = nullptr;
}

拷贝构造

//v2(v1);
// 传统写法
vector(const vector<T>& v)  // 拷贝构造, 默认为浅拷贝
{
	_start = new T[v.capacity()];
	_finish = _start;
	_endofstorage = _start + v.capacity();
	for (size_t i = 0; i < v.size(); i++)
	{
		*_finish = v[i];
		_finish++;
	}
}
 //提高复用的写法
vector(const vector<T>& v)
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstorage(nullptr)
{
	reserve(v.capacity()); // 这不写也行,因为在push_back中会有增容的操作,但是先开辟好相同的空间,提高性能
	for (const auto& e : v)
		push_back(e);
}

operator= 

// 赋值运算符重载,默认的赋值运算符重载为浅拷贝
// 传统写法
vector<T>& operator=(const vector<T>& v) 
{
	if (this != &v)
	{
		delete[] _start;
		_start = new T[v.capacity()];
		memcpy(_start, v._start, sizeof(T) * v.size());
		_finish = _start + v.size();					// 注意这里要把_finish _endofstorage更新!!!!
		_endofstorage = _start + v.capacity();
	}
	return *this;
}
 //现代写法
 //v2 = v1
vector<T>& operator=(vector<T> v)   // 这里是传值,会自动调用拷贝构造, 然后v会单独开辟一块空间
{
	swap(v);
	return *this;					// 最后作用域结束,v会自动调用析构函数,释放掉v2的旧空间
}
void swap(vector<T>& v)          // 当对于两个vector对象直接使用swap交换,那会产生三次深拷贝,代价极大
{									// 定义一个成员函数swap, 只需要交换三次指针,提升性能
	::swap(_start, v._start);		// 区分成员函数的swap和库中的swap函数,在函数名之前加std::
	::swap(_finish, v._finish);		// 
	::swap(_endofstorage, v._endofstorage);
}

迭代器和 size 、 capacity

iterator begin()
{
	return _start;
}
iterator end()
{
	return _finish;
}
const_iterator begin() const
{
	return _start;
}
const_iterator end() const
{
	return _finish;
}
size_t size()
{
	return _finish - _start;
}
size_t capacity()
{
	return _endofstorage - _start;
}
const size_t size()	const
{
	return _finish - _start;
}
const size_t capacity() const
{
	return _endofstorage - _start;
}

reserve

当 n 大于 capacity,扩容
当 n 小于 capacity,什么也不做

void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t sz = size();   // 旧的_start的长度要先存起来,因为当扩容时,旧的空间会被释放,那么就找不到旧空间的长度了
		T* temp = new T[n];
		if (_start)
		{
			for (size_t i = 0; i < sz; i++)
				temp[i] = _start[i];
			delete[] _start;
		}
		_start = temp;
		_finish = _start + sz; // 这里不可以 + size(), 因为原来的_start 移动了
		_endofstorage = _start + n;
	}
}

ps:在扩容的时候,拷贝旧空间时,不可以使用memcpy(temp, _start, sz * sizeof(T));  

假如T的类型不是内置类型,比如是string,那么在v中存储的string类型的对象,每个string类型的对象中都有一个指针(char* _str, size_t _size, size_t _capacity)
这个指针指向的是在堆区开辟的空间,这块空间就是这个string类型对象的内容
当需要扩容的时候,temp先开辟空间,然后temp拷贝旧空间的内容
假如是使用memcpy实现拷贝,(按字节拷贝,浅拷贝)那么在temp中拷贝旧空间的string类型对象,该对象中也一样会有_str指针,并且和旧空间的是独立的(也就是说这两个指针的地址不同)
但是两个指针都指向同一块堆区的空间。
然后delete去释放旧vector中的每一个string类型对象,当释放每一个string类型对象的时候,又会调用string类的析构函数
那么就会把堆区的内容也释放掉,这时:temp中的string类型对象的_str指针指向的内容也被释放了,指向空,如果访问的话,就会访问随机值
那如何避免这个问题呢?
使用更深层次的深拷贝,temp[i] = _start[i]; 这是两个string类型对象的赋值操作
那么就会调用string类型的operator=函数,这时就会进行深拷贝, 再次在堆区开辟一块新空间,拷贝原来的堆区内容。

resize

// 这里的T(),是一个匿名对象做缺省值
void resize(size_t n, const T& val = T())
{
	if (n > capacity())
		reserve(n);
	if (n > size())
	{
		while (_finish < _start + n)
		{
			*_finish = val;
			++_finish;
		}
	}
	else
		_finish = _start + n;
}

insert 、push_back

iterator insert(iterator pos, const T& val)
{
	assert(pos <= _finish && pos >= _start);
	if (_finish == _end_of_storage)
	{
		size_t n = pos - _start;
		reserve(capacity() == 0 ? 4 : 2 * capacity());
		pos = _start + n; // 扩容之后,迭代器失效,需要重新赋值
	}
	iterator end = _finish;
	while (end != pos)
	{
		*end = *(end - 1);
		--end;
	}
	*end = val;
    _finish++;
    return pos;
}
void push_back(const T& val)
{
	insert(_finish, val);
}

erase、pop_back

iterator erase(iterator pos)
{
	assert(pos < _finish);
	iterator end = pos;
	while (end < _finish - 1)
	{
		*end = *(end + 1);
        ++end;
	}
	_finish--;
	return pos;
}
void pop_back()
{
	erase(_finish - 1);
}

operator []

T& operator[](size_t pos)
{
	return _start(pos);
}
const T& operator[](size_t pos) const
{
	return _start(pos);
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值