vector顺序表

目录

1.vector的介绍

2.vector的定义 

 3.vector的迭代器

3.1begin(),end()

3.2rbegin(),rend()

3.3cbegin(),cend(),crbegin(),crend()

4.容量操作

 4.1size()

4.2max_size()

4.3resize()

4.4capacity()

4.5empty()

4.6reverse()

4.7shirink_to_fit()

5.访问

5.1[]

 5.2at

5.3front,back

5.4data

6.修改 

6.1assign()

6.2push_back和pop_back()

6.3insert()

6.4erase()

6.5swap()

6.6clear()

6.7关于vector的find(不是成员函数)

6.8其他

7运算符重载

8.vector模拟


1.vector的介绍

注意,我是先写的strin

跟我string的文章一样,这种介绍网上太多了,我就直接cv了

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

2.vector的定义 

4种定义方式(98文档里还是4种,14有6种。但现在有些编译器,已经有很多种了,我vs里面就有10种构造函数)

第一种,就是无参构造,也就是

vector (const allocator_type& alloc = allocator_type());
默认占据的空间大小取决于编译器。数据个数是0
vector<int>o;
注意,vector是个模板,我们给什么类型参数,实例化后就会是什么类型的顺序表
比如这里给了int,那么实例化后,o这个顺序表里面数据的数据类型就只能是int类型

第二种,给定空间大小和指定值

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

vector<int>ans(10),这样顺序表的size就会变成10,
具体的值,一般来说都是默认0(取决于编译器)

这里再补充下,我自己其实也不太清楚这样的写法是遵循哪个构造函数的,
因为现在vector我定义的时候,
vs编译器给我弹了10种定义方式,我也不知道这样写是满足哪种。
vector<vector<int>>ans(10,vector<int>(10))
这样ans是个二维的顺序表,且每个元素都是一维的顺序表,
并且初始ans的size是10,一维顺序表(元素)的size也是10.

第3种,给迭代器的方式

vector (InputIterator first, InputIterator last,
                 const allocator_type& alloc = allocator_type());
vector<int>o;
vector<int>a(o.begin(), o.end());

注意,这里的迭代器不限定一定是int类型的vector
vector<char>o = { 's','d','g' };
vector<int>a(o.begin(), o.end());
那么a顺序表里存的就是s,d,g的ascll码了。

同样也不限定是vector的迭代器
string o = { "dwadwada" };
vector<int>a(o.begin(), o.end());
同理,存的也是字符的ascll码

其他的容器没试过,我们平时可以试试

第4种,也就是非常简单的拷贝构造

vector (const vector& x);

vector<int>o = { 353533 };
vector<int>a(o);

 这里补充下,就是赋值操作

vector<int>ans = { 3,4,5,6,7,8 };
	vector<int>ans1 = ans;
	vector<int>ans2;
	ans2 = ans;

 3.vector的迭代器

3.1begin(),end()

	vector<int>ans = { 3,4,5,6,7,8 };
	vector<int>::iterator it = ans.begin();
	while (it != ans.end())
	{
		cout <<(*it) << " ";
		it++;
	}
比较经典的迭代器操作
用的时候当成指针就好,反正平时也就拿来正向遍历容器。
具体迭代器的内容,看情况,我再发

3.2rbegin(),rend()

	vector<int>ans = { 3,4,5,6,7,8 };
	vector<int>::reverse_iterator it = ans.rbegin();
	while (it != ans.rend())
	{
		cout <<(*it) << " ";
		it++;
	}
是倒着遍历的

3.3cbegin(),cend(),crbegin(),crend()

这个就是加了const修饰

	const vector<int>ans = { 3,4,5,6,7,8 };
	vector<int>::const_iterator it = ans.begin();
	while (it != ans.end())
	{
		cout <<(*it) << " ";
		//const_iterator是保护*it不被修改
		//const vector<int>::const_iterator it 是保护it不被修改
		it++;
	}

  而加了c,就是说传递的迭代器是const_iterator类型的

4.容量操作

vs下capacity是1.6倍增长,gcc下是2倍

 4.1size()

	vector<int>ans = { 3,4,5,6,7,8 };
	cout << ans.size();
返回当前有效数据个数

4.2max_size()
 

	vector<int>an;
	cout << ans.max_size();
返回的值根据环境决定
没多大用

4.3resize()

resize既可以影响容量也可以影响数据,比如给的值大于capacity,那么会自动扩容,且会填充默认值在string对象里,如果不给数据,填充的是0,如果给定数据比如a1.resize(100,3)那么会填充3。

如果给定值大于size但小于capacity呢,在vs和g++环境下,size会变成给定值,capacity不会变。

如果给定值小于size呢,在vs环境下,size会变成给定值,capacity不会变,相当于会保留前给定值个数据,多出来的数据相当于删除。

	vector<int>ans = { 3,4,5,6,7,8 };
    此时size是6,capacity根据编译器决定,可能大于6,
	ans.resize(100,3);
    此时capacity是100,多出来的94个元素都是3

4.4capacity()

	vector<int>ans = { 3,4,5,6,7,8 };
	cout << ans.capacity();
返回现在顺序表开辟了多大的空间

4.5empty()

	vector<int>ans = { 3,4,5,6,7,8 };
	if (!ans.empty())cout << ans[0];
判断当前顺序表是不是空的
是返回true,不是返回false

4.6reverse()

capacity一般我们不用管,因为顺序表会自动扩容。但reserve也有意义,可以减少不必要的重复扩容操作,可以一步到位(前提是我们自己确定要多少空间)而且reserve设置的空间也是要按规律走的,比如vs下的15,31,47,我们如果reserve给的不是这些值,编译器开的时候会自动向这么数值上靠,比如给40,可能开的47。但也不一定,有一定随机性。但这是vs环境,别的环境可能也不一样(比如g++下就比较标准,直接按你给的值开)

另外,关于reserve能不能缩空间呢,vs环境下是不能缩的,不管是否有数据,但是在g++环境下,可以缩(如果无数据,就按你给的缩,有数据并且给的值小于数据空间大小,就缩到数据的长度(简而言之,就是在不影响数据的情况下缩))

但不管什么环境,可以确定的是,不会影响数据

	vector<int>ans = { 3,4,5,6,7,8 };
	ans.reserve(100);
只影响capacity
不影响size

4.7shirink_to_fit()

	vector<int>ans = { 3,4,5,6,7,8 };
	ans.reserve(100);
	cout << ans.capacity() << endl;
此时是100
	ans.shrink_to_fit();
	cout << ans.capacity();
此时就是6

让容量跟现有size()匹配,不影响数据

5.访问

5.1[]

注意,[]的重载之后,既可以返回该下标的元素,也能对该下标字符进行修改
因为有两种重载,一个是返回const 类型字符的引用,一个是返回普通字符的引用
虽然非const可以调用const的那条重载,但是const的不能调用非const的那条重载

所以有2条。而且利用const,可以避免修改const类型的字符串。

 5.2at

在使用类似,格式稍微变化,但是注意,[]对于越界访问是通过assert断言的,而at是通过异常,异常看我的其他的文章(目前还没写,略略略)。

5.3front,back

front是返回首元素,跟[]一样,有两个重载,可以修改,可以返回。back同理

5.4data

本质就是将顺序表中数组的部分返回,返回数组的地址,所以用指针接受,利用[]本质是地址增减,同样做到对数组的访问和修改

6.修改 

6.1assign()

注意,assign是替换

	vector<int>ans = { 3,4,5,6,7,8 };
	vector<int>b;
	b.assign(ans.begin(), ans.end());
	//迭代器给个左闭右开的区间,然后替换掉。
	//此时b有6个元素
	int ab[3] = { 0,1,2 };
	b.assign(ab, ab + 3);
	//将ab数组的直接替换掉b原来的数组,此时b只有3个元素
	b.assign(5, 100);
	//此时b的元素一共5个,每个元素都是int类型的100

6.2push_back和pop_back()

	vector<int>ans = { 3,4,5,6,7,8 };
	ans.push_back(3);
	//此时ans是3,4,5,6,7,8,3
	ans.pop_back();
	//此时ans是3,4,5,6,7,8
push是尾插
pop是尾删

6.3insert()

	vector<int>ans = { 3,4,5,6,7,8 };
	vector<int>::iterator h = ans.begin();
	while ((*h)!=6)
	{
		h++;
	}
	//ans.insert(h, 4);
	//3 4 5 4 6 7 8
	//第一种是给迭代器,在迭代器指向的位置前面插入数据


	ans.insert(h, 10, 3);
	//3 4 5 3 3 3 3 3 3 3 3 3 3 6 7 8
	//第二种是在指定位置前面添加n个指定的数据,比如这里是10个3
	vector<int>b;
	b.insert(b.begin(), ans.begin(), ans.end());
	//此时b是3 4 5 3 3 3 3 3 3 3 3 3 3 6 7 8
	int a[3] = { 2,4,5 };
	b.insert(b.begin(), a, a + 3);
	//此时b是2 4 5 3 4 5 3 3 3 3 3 3 3 3 3 3 6 7 8
	//这两种都是在被插入的顺序表给定的迭代器指向的位置,开始插入给定的区间里的数据

6.4erase()

	vector<int>ans = { 3,4,5,6,7,8 };
	vector<int>::iterator h = ans.begin();
	while ((*h)!=6)
	{
		h++;
	}
	//ans.erase(h);
	//3 4 5 7 8
	//第一种是删除迭代器指向的数据
	
	ans.erase(h,ans.end());
	//3 4 5
	//删除迭代器指定的区间里的数据

6.5swap()


	vector<int>ans = { 3,4,5,6,7,8 };
	vector<int>bns = { 5,5,5,5,5 };
	ans.swap(bns);
	//此时ans是5,5,5,5,5
	//此时bns是3,4,5,6,7,8

6.6clear()
 

	vector<int>ans = { 3,4,5,6,7,8 };
	ans.clear();
	//清空数据(size==0),容量不清空

6.7关于vector的find(不是成员函数)

(注意,stl的vector里面没有find的成员函数,因为对于vector和list这种容器,find完全可以写入通用函数,也就是algorithm这个头文件中)

	vector<int>ans = { 3,4,5,6,7,8 };
	vector<int>::iterator it = find(ans.begin(), ans.end(), 3);
	cout << (*it);
注意,从给定的左端点开始往右端点找

6.8其他

其实还有个emplace,但这个要把可参数模板(好像是叫这个)学了先,我写这篇文章的时候还没学,xdm等我哪天想起来了再补吧

7运算符重载

template <class T, class Alloc>
  bool operator== (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs);
(2)	
template <class T, class Alloc>
  bool operator!= (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs);
(3)	
template <class T, class Alloc>
  bool operator<  (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs);
(4)	
template <class T, class Alloc>
  bool operator<= (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs);
(5)	
template <class T, class Alloc>
  bool operator>  (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs);
(6)	
template <class T, class Alloc>
  bool operator>= (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs);

自己看吧,照习惯用就好

8.vector模拟

#pragma once
#include<string.h>
#include<assert.h>
namespace zl
{
	//模板,根据传的类型,决定生成什么类型的模板
	template<class T>
	class vector
	{
	public:
		//定义迭代器
		typedef T* iterator;
		typedef const T* const_iterator;
		//构造函数,赋值空,如果继续优化,可以直接在private那边的变量声明中将变量的缺省值直接设置为空
		//这样无参构造和下面没有被注释的拷贝构造,不用赋值这三个变量了
		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{}
		/*vector(const vector<T>&v)
		{
			_start = new T[v.capacity()];
			memcpy(_start, v._start, sizeof(T) * v.size());
			_finish = _start + v.size();
			_end_of_storage = _start + v.capacity();
		}*/
		//拷贝构造
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			reserve(v.capacity());//只用扩容一次
			for (auto& e : v)
			{
				push_back(e);
			}
		}
		//泛型的利用迭代器区间初始化(普通数组也可以通过给指针的形式,因为指针是天然的迭代器,物理上连续,注意其他容器的迭代器不一定是指针)
		template<class InputIterator>
		vector(InputIterator first, InputIterator end)
		{
			while (first != end)
			{
				push_back(*first);
				first++;
			}
		}
		//匹配问题所以我们要加个int类型的
		vector ( size_t n, const T& val = T())
		{
			resize(n, val);
		}
		vector(int n, const T& val = T())
		{
			resize(n, val);
		}
		//交换
		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;
		}
		//两个经典开头和结尾,注意,左闭右开
		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}
		//为了防止传const修饰的实体类
		const_iterator begin()const
		{
			return _start;
		}
		const_iterator end()const
		{
			return _finish;
		}
		//尾插,注意扩容
		void push_back(const T& x)
		{
			if (_finish == _end_of_storage)
			{
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
			}
			*_finish = x;
			++_finish;
		}
		//经典操作,--即可
		void pop_back()
		{
			assert(size() > 0);
			--_finish;
		}
		//这里我们要注意如果发生了扩容,pos迭代器会失效(我们这里vector迭代器是指针,也就是说pos变成了野指针)
		//所以要用len记录长度,从而更新pos
		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 : capacity() * 2);
				pos = _start + len;
			}
			//memmove(pos + 1, pos, sizeof(T) * (_finish - pos));
			// 这里也有问题,浅拷贝
			//我们要用深拷贝,否则自定义类型会出事
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
			_finish++;
			return pos;
			//返回值的原因看下面erase的,也是迭代器失效的问题
			//本质除了环境强制检查外,就是存储数据的空间发生了变化,造成的迭代器失效
		}
		iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);
			iterator it = pos + 1;
			while (it < _finish)
			{
				*(it - 1) = *it;
				it++;
			}
			_finish--;
			return pos;
			//注意,为什么要有返回值?
			//因为在我们这个版本中erase的删除是不采取缩容实现。
			//何为缩容实现,比如1 3 5 6,我们删了5,缩容实现就是
			//将1 3 6放在一个新的空间里,而不是如我们现在这样写的
			//在原空间上向前覆盖的形式
			//那这样,如果pos迭代器我们在外面还会继续使用
			//就像reserve那一样,是会出现迭代器失效的问题。
			//还有,如果在vs环境下,会强制检查迭代器,假如
			//迭代器放入了erase,insert这些函数中,那么就不能再用
			//vs会直接强制检查。
			//因此,我们采用返回值的形式,这样不管是强制检查,还是采取缩容
			//我们都可以利用返回值重新覆盖外面pos迭代器的方式来完成我们的代码
		}

		//注意,c++为了兼容模板,升级了内置类型,内置类型也有构造函数
		//因为不确定里面T到底什么类型,所以缺省值,我们干脆用相应的无参构造即可
		void resize(size_t n, T val = T())
		{
			if (n > size())
			{
				reserve(n);
				while (_finish < _start + n)
				{
					*_finish = val;
					_finish++;
				}
			 }
			else
			{
				_finish = _start + n;
			}
		}
		//扩容操作,注意申请的新空间,记得把之前的东西复制过来
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t _size = size();
				T* tmp = new T[n];
				if (_start)
				{
					//memcpy(tmp, _start,  _size* sizeof(T));这个没法处理自定义类型(像string这样内部很可能有指针指向其他空间)
					//也就是浅拷贝和深拷贝的问题
					for (int i = 0; i < _size; i++)
					{
						tmp[i] = _start[i];
						//如果是自定义类型,会调用赋值操作符,并且都是深拷贝(像std这样都是有考虑深拷贝的)
						//如果用内存池,利用定位new,显示调用构造函数,也可以做到
					}
				}

				delete[] _start;
				_start = tmp;
				_finish = _start + _size;
				_end_of_storage = _start + n;
			}
		}
		//经典size,有效数据个数
		size_t size() const
		{
			return _finish - _start;
		}
		//容量
		size_t capacity()const
		{
			return _end_of_storage-_start;
		}
		//经典[]操作符重载
		T& operator[](size_t pos)
		{
			assert(pos < size());
			return *(_start + pos);
		}
		//可读不可改
		const T& operator[](size_t pos) const
		{
			assert(pos < size());
			return *(_start + pos);
		}
		//析构函数
		~vector()
		{
			if (_start)
			{
				delete[] _start;
				_start = _finish = _end_of_storage = nullptr;
			}
		}
	private:
		//开始,结束,容量
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};

}

  • 12
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
C++中可以使用动态分配的顺序表来实现动态数组,其中vectorC++标准库中提供的一个动态数组容器[^2]。下面是一个使用vector实现动态分配顺序表的示例代码: ```cpp #include <iostream> #include <vector> int main() { std::vector<int> seqList; // 创建一个空的动态分配顺序表 // 在顺序表末尾插入元素 seqList.push_back(10); seqList.push_back(20); seqList.push_back(30); // 获取顺序表的长度 int length = seqList.size(); // 遍历顺序表 for (int i = 0; i < length; i++) { std::cout << seqList[i] << " "; } std::cout << std::endl; // 删除顺序表中的元素 seqList.pop_back(); // 查找顺序表中的元素 int element = 20; auto it = std::find(seqList.begin(), seqList.end(), element); if (it != seqList.end()) { std::cout << "Element " << element << " found at index " << std::distance(seqList.begin(), it) << std::endl; } else { std::cout << "Element " << element << " not found in the sequence list" << std::endl; } return 0; } ``` 这段代码使用了vector容器来实现动态分配顺序表。首先,我们创建了一个空的动态分配顺序表seqList。然后,我们使用push_back函数在顺序表的末尾插入元素。接下来,我们使用size函数获取顺序表的长度,并使用for循环遍历顺序表中的元素。然后,我们使用pop_back函数删除顺序表中的最后一个元素。最后,我们使用find函数查找顺序表中的元素,并使用distance函数计算元素在顺序表中的索引。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值