【STL容器】vector

文章目录

  • 前言
  • vector
    • [1.1 vector的定义](#1.1 vector的定义)
    • [1.2 vector的迭代器](#1.2 vector的迭代器)
    • [1.3 vector的元素操作](#1.3 vector的元素操作)
      • [1.3.1 Member function](#1.3.1 Member function)
      • [1.3.2 capacity](#1.3.2 capacity)
      • [1.3.3 modify](#1.3.3 modify)
    • [1.4 vector的优缺点](#1.4 vector的优缺点)

前言

vector是STL的容器,它提供了动态数组的功能。

注:文章出现的代码并非STL库里的源码,只是对源码的简单化处理


vector

1.1 vector的定义

vector的成员变量是3个迭代器 ,用来管理一段线性连续空间

具体如下图:

1.2 vector的迭代器

vector的迭代器是指针,且属于随机迭代器

vector所管理的是一个连续线性的空间,因此无论类型如何,普通的指针就可以很好的解决问题。
注:
随机迭代器 ,在支持双向移动的基础上,支持前后位置的比较、随机存取、直接移动n个距离。

模拟如下:

cpp 复制代码
template<class T>
class vector
{
public:
	typedef T* iterator;
	typedef const T* const_iterator;

private:
	iterator _start;
	iterator _finish;
	iterator _end_of_storage;
};

1.3 vector的元素操作

1.3.1 Member function

构造函数

这里我们先不管空间配置器(allocator),以及explicit关键字.

简化上面的3个红框的部分

cpp 复制代码
//1.
vector();
//2.
vector(size_t n, const T& t = T());
//3.
template<class InputIterator>
vector(InputIterator first, InputIterator last);

简化后1和2就容易理解了,但可能对于3还有些困惑

它的使用如下:

简化实现如下:

cpp 复制代码
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
	while(first != last)
	{
		this->push_back(*first);
		++first;
	}
}

赋值运算符重载

对于赋值运算符重载函数,有一种现代的写法

cpp 复制代码
//现代写法
void swap(vector<T>& tmp)
{
	std::swap(_start, tmp._start);
	std::swap(_finish, tmp._finish);
	std::swap(_end_of_storage, tmp._end_of_storage);
}
vector<T>& operator=(vector<T> tmp)
{
	swap(tmp);
	return *this;
}
//传统写法
vector<T>& operator=(const vector<T>& v)
{
	if(this != &v)
	{
		clear();
		detele[] _start;
		iterator tmp = new T[v.capacity()]
		_start = tmp;
		_finish = tmp + v.size();
		_end_of_storage = tmp + v.capacity();
		for(size_t i = 0; i < v.size(); i++)
		{
			_start[i] = v[i];
		}
	}
	return *this;
}

我们浅浅分析一下,

传统的赋值逻辑是先将原空间删除,再开辟一个新空间,然后将数据慢慢拷贝过去。

现代的赋值逻辑,operator=的形参tmp不是引用,是通过拷贝构造 得到的局部对象,然后通过swap(), 将tmp与this的数据进行交换,这样this指向了它想要的空间,而tmp指向了旧空间,又因为tmp是局部对象,等operator=函数结束时,tmp会自动调用析构函数 ,释放tmp指向的空间,也就是原来的旧空间。

相比原来的旧空间,不但减少了代码,更减少了错误。

注:有些人在用传统方法写赋值运算符重载时,会犯一个严重的错误,就是使用memcpy对原来的数据进行拷贝。

cpp 复制代码
错误:memcpy(_start, v._start, sizeof(T)*v.size());
正确:for(size_t i = 0; i < v.size(); i++)
{ _start[i] = v[i]; }

这是比较典型的值拷贝问题,如果vector存储的类型T是内置类型,没什么事,但如果是自定义类型, 指针则会出错


1.3.2 capacity

上面的问题也容易出现在reserve
reserve()

作用:开辟空间到n,但如果n < capacity,则不会变。总结一句,只接受变大。

cpp 复制代码
void reserve(size_t n)
		{
			
			if (n > capacity())
			{
				size_t len = size();
				iterator tmp = new T[n];
				for (size_t i = 0; i < len; i++)
				{
					tmp[i] = _start[i];
				}
				delete[] _start;
				_start = tmp;
				_finish = _start + len;
				_endofstorage = _start + n;
			}
		}

resize

强制将size改到n

  1. 如果sz > n,则删除数据
  2. 如果sz < n,则扩容,并填入t
cpp 复制代码
void resize(size_t n, const T t = T())
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else
			{
				if (n > capacity()) reserve(n);
				for (iterator i = _finish; i < _start + n; i++)
				{
					*i = t;
				}
				_finish = _start + n;
			}
		}

1.3.3 modify

这里介绍一些insert和erase

erase()

模拟:

cpp 复制代码
iterator erase(iterator pos)
{
    assert(pos >= _start && pos < _finish);
    iterator it = pos;
    while (it != _finish)
    {
        *it = *(it + 1);
        ++it;
    }
    --_finish;
    return pos;
}
iterator erase(iterator first, iterator last)
{
	assert(first >= _start && first < _finish);
	assert(last >= _start && last <= _finish);
	assert(first<= last);
	iterator it = first;
	while(last != _finish)
	{
		*it = *last;
		++it, ++last;	
	}
	_finish = it;
	return it;
}

insert

cpp 复制代码
void insert(iterator pos, const T& t)
{
	assert(pos >= _start && pos <= _finish);
	if (_finish == _endofstorage)
	{
		size_t n = capacity() == 0 ? 4 : 2 * capacity();
		reserve(n);
	}
	iterator end = _finish;
	while (end > pos)
	{
		*end = *(end - 1);
		--end;
	}
	*pos = t;
	_finish++;
}

注:在对容器的修改,一定要注意迭代器失效的问题

以erase为例

在某些平台,上面的代码是会报错的,比如vs编译器。

逻辑是这样的,v.erase(it)将it所对应位置的数据删除,删除后it就不应该具有再次访问的权利,因为这有非法访问的可能。对于vector这种连续的空间,删除数据后,后面的数据会往前补上。但如果是像list的这样的链表,删除后,iterator指向的将是一块已经被释放的空间。这很明显不对。因此,编译器决定将这样的迭代器判定为失效。但并不是所有的编译器都会这样做。比如linux下的g++对于上面的代码就不会报错。


1.4 vector的优缺点

std::vector 是 C++ STL 提供的一个非常有用的动态数组容器,它有许多优点和一些局限性,以下是其主要优缺点:

优点:

  1. 快速随机访问 :由于元素在内存中是连续存储的,vector 允许在常量时间内进行随机访问,这意味着可以通过索引快速访问任何元素。

  2. 动态大小vector 允许在运行时动态增加或减少其大小,这使得它非常适合需要动态分配内存的情况。

  3. 自动内存管理vector 会自动处理内存的分配和释放,无需手动管理内存,这可以减少内存泄漏和其他与手动内存管理相关的问题。

  4. 迭代器支持vector 支持迭代器,你可以使用迭代器来遍历容器中的元素,执行各种操作,如查找、排序等。

  5. 元素复制和移动vector 可以容纳各种数据类型,包括内置类型和用户自定义类型。它会在容器内部复制或移动元素的副本,而不影响原始对象。

缺点:

  1. 插入和删除效率较低 :在 vector 中插入或删除元素涉及到移动其他元素,因此插入和删除操作的效率较低。如果需要频繁的插入和删除操作,可能有更适合的容器,如 std::liststd::deque

  2. 内存浪费vector 会预留一定的内存容量以容纳更多的元素,这可能导致内存浪费,特别是当你不确定容器的最终大小时。可以使用 reserve() 来手动管理内存分配,但仍可能存在浪费。

  3. 固定的增长策略vector 的内部实现通常采用固定的增长策略,即每次扩容都会分配一块更大的内存并复制元素。这可能导致一些性能问题,特别是在大规模元素插入时。如果需要更高效的动态数组,可以考虑使用 std::deque 或其他容器。

  4. 不适用于复杂的插入操作 :如果需要在中间或开头插入元素并保持高效性能,vector 可能不是最佳选择,因为插入元素后需要移动大量元素。

相关推荐
c4fx8 分钟前
Delphi5利用DLL实现窗体的重用
开发语言·delphi·dll
鸽芷咕32 分钟前
【Python报错已解决】ModuleNotFoundError: No module named ‘paddle‘
开发语言·python·机器学习·bug·paddle
Jhxbdks41 分钟前
C语言中的一些小知识(二)
c语言·开发语言·笔记
java66666888841 分钟前
如何在Java中实现高效的对象映射:Dozer与MapStruct的比较与优化
java·开发语言
Violet永存42 分钟前
源码分析:LinkedList
java·开发语言
代码雕刻家44 分钟前
数据结构-3.1.栈的基本概念
c语言·开发语言·数据结构
Fan_web1 小时前
JavaScript高级——闭包应用-自定义js模块
开发语言·前端·javascript·css·html
梦想科研社1 小时前
【无人机设计与控制】四旋翼无人机俯仰姿态保持模糊PID控制(带说明报告)
开发语言·算法·数学建模·matlab·无人机
风等雨归期1 小时前
【python】【绘制小程序】动态爱心绘制
开发语言·python·小程序
千穹凌帝1 小时前
SpinalHDL之结构(二)
开发语言·前端·fpga开发