《C++ vector详解》

目录

1.结构

2.构造函数

无参构造

指定参数构造

迭代器构造

初始化列表构造

3.拷贝构造

4.析构函数

5.遍历

重载【】

5.插入

扩容

尾插

指定位置插入

6.删除

尾删

指定位置删除


1.结构

vector是一个类似于数组一样的容器,里面可以放各种各样的元素。我们只需在其中储存空间的地址即可,所以包含三个私有元素,分别是_start_finish_end_of_storage。使用模板T可以使vector储存各种各样的元素。将元素指针类型typedef成迭代器。代码如下。

        template <class T>
        typedef T* iterator;    
        typedef const T* const_iterator;
private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;

2.构造函数

包含无参构造指定参数构造迭代器构造初始化列表构造

无参构造

不用传参,使用缺省参数或者初始化列表中的参数。

指定参数构造

第一个参数n表示空间大小,第二个参数const T& val = T()是一个缺省参数,当没有传递第二个参数的时候,会调用T的默认构造函数,用val来引用它。比如说T是int类型,那么val就是0的引用所以这里要加const,然后再用val去初始化剩余的空间

为什么要n有size_t和int两种类型呢?

如果只定义size_t,那么传参(6,5)的时候,编译器默认6是int类型,与size_t类型不符合,那么就会找更匹配的构造函数,这时候会找到迭代器构造,而此时就会出现非法的间接寻址(int类型的值无法解引用)编译错误。

如果只定义int,那么传递的参数指定是size_t类型,如(6u,5)(后缀加u表示unsign int类型),同样会产生相同的问题。所以两者都显示定义了。

迭代器构造

参数为迭代器,迭代器是与指针类似的东西,可以进行解引用,+,-等操作。

这里为什么不使用前面的迭代器iterator,而是要使用一个成员函数模板?前面的iterator只能表示vector容器的迭代器,当我们想用其他容器的迭代器来初始化vector的时候就不适用了,所以要定义一个新的函数模板。

其实我自己在学的时候还有一个疑问就是为什么std::list<double> iterator能初始化vevtor<int>?

一是本质上使用的是(*first)来初始化的,拿到的是double类型的元素,

二是这其中实际上涉及到了隐式类型转换将double转换成了int。

初始化列表构造

std::initializer_list<T>是C++11 标准类型,专门处理花括号 {} 初始化(如 {1, 2, 3},之后再遍历尾插。这种方法可以避免重复地push_back。

//无参
vector<int> v1;
//指定参数
vector<int> v2(6);
//迭代器构造
string s1("wit lzk");
vector<int> v3(s1.begin(), s1.end());
//花括号
vector<int> v4 = { 1,2,3,5 };
//构造函数
		vector()
		{}
		 
		vector(size_t n, const T& val = T())
		{
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
		}
		vector(int n, const T& val = T())
		{
			reserve(n);
			for (int i = 0; i < n; i++)
			{
				push_back(val);
			}
		}
		template <class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}
		vector(std::initializer_list<T> il)
		{
			reserve(il.size());
			for (auto t& : il)
			{
				push_back(t);
			}
		}

3.拷贝构造

旧版拷贝构造分为开空间,遍历,拷贝

//拷贝构造
		vector(const vector<T>& v)
		{
			reserve(v.capacity());
			for (auto& t : v)
			{
				push_back(t);
			}
		}

赋值本质上也复用了拷贝构造。比如说v2 = v1把v1赋值给v2,v1进行传值传参,调用拷贝构造函数,v1会生成一个临时副本拷贝给v,然后利用swap将v的值和v2交换,实现赋值操作。同时v是一个临时对象,在和v2进行交换后函数结束时就会将原空间析构。

        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;
		}

4.析构函数

析构函数还是看构造函数构造了什么,析构函数就析构什么。

/析构函数
		~vector()
		{
			delete[] _start;
			_start = _finish = _end_of_storage = nullptr;
		}

5.遍历

得到对象空间大小,和实际数据个数。

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

重载【】

T operator[](size_t n)
{
	assert(n < size());
	return _start[n];
}
vector<int> v1 = { 1,2,3,4 };
//[]遍历
for (size_t t = 0; t < v1.size(); t++)
{
	cout << v1[t] << " ";
}
cout << endl;
//范围for遍历
for (auto ch : v1)
{
	cout << ch << " ";
}
cout << endl;

5.插入

插入前需要判断空间是否足够,不够需要扩容。拷贝数据到新空间时,不能使用memcpy,如果是内置类型还好,如果是自定义类型,浅拷贝将会导致空间析构两次。所以直接暴力拷贝数据。因为是异地扩容,所以拷贝完毕要重新给类成员变量赋值。

扩容

//扩容
void reserve(size_t n)
{
	//扩容是改变(_end_of_storage-start)的值
	/*if (n <= _end_of_storage - _start)
	{
		_finish = _start + n;
	}*/
	 if(n > capacity())
	{
		 size_t pre_size = size();
		T* tmp = new T[n];
		//_start不能为空
		if (_start)
		{
		//使用memcpy为浅拷贝,资源会被析构两次
		//memcpy(tmp, _start, sizeof(_start));
			for (size_t i = 0; i < size(); i++)
			{
				tmp[i] = _start[i];
			}
			//数据拷贝完要把旧空间删除
			delete[] _start;
		}
		 
		_start = tmp;
		_finish = _start + pre_size;
		_end_of_storage = _start + n;
	}
}

尾插

//尾插
void push_back(const T& v)		 
{
	size_t len = _finish - _start;
	//写法太冗余
	//if (_finish < _end_of_storage)
	//{
	//	_start[len] = v;
	//}
	//else {
	//	//空间不够扩容
	//	reserve(capacity() == 0 ? 4 : 2 * capacity());
	//	_start[len] = v;
	//}
	//_finish++;
	//相等就扩容
	if (_finish == _end_of_storage)
	{
		reserve(capacity() == 0 ? 4 : 2 * capacity());
	}
	//无论满没满都要进行这两步
	(*_finish) = v;
	_finish++;
}

指定位置插入

这里需要注意迭代器失效的问题,异地扩容后_start被改变,pos也就失效了。所以要先记录pos到_start的距离,扩容后重新赋值。

//指定位置插入
iterator insert(iterator pos, const T& x)
{
	assert(pos >= _start);
	assert(pos <= _finish);
	if (_finish == _end_of_storage)
	{
		size_t len = pos - _start;
		reserve(capacity() == 0 ? 4 : 2 * capacity());
		//扩容完后的_start会改变
		//原来的pos会失效(迭代器失效了)
		pos = _start + len;
	}
	//移动数据
	iterator it = _finish;
	while (it >= pos)
	{
		*it = *(it - 1);
		it--;
	}
	*pos = x;
	_finish++;
	return pos;
}

6.删除

尾删

改变一下_finish的位置即可。


//尾删
void pop_back()
{
	assert(_finish > _start);
	_finish--;

}

指定位置删除

那么在这个地方vs实现的vector会出现迭代器失效的问题。vs会认为erase一次后it就失效了,所以必须在使用erase之后,重新给it赋值。 当然自己实现的vector中不会出现这个问题。

vector<int> v = { 1,1,2,3,3,4,5,6,7,8,9,9 };
for (auto ch : v)
{
	cout << ch << " ";
}
cout << endl;
//删除所有奇数
vector<int>::iterator it = v.begin();
while (it != v.end())
{
	if (*it % 2 == 1)
	{
        //重新赋值
		it = v.erase(it);
	}
	else {
		it++;
	}
}
for (auto ch : v)
{
	cout << ch << " ";
}
cout << endl;
iterator erase(iterator pos)
{
	assert(pos >= _start);
	assert(pos <= _finish);
	iterator it = pos;
	while (it < _finish)
	{
		*it = *(it + 1);
		it++;
	}
	_finish--;
	return pos;
}

"Happy Coding! 🚀" 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值