大话STL第二期——机械先驱—vector

发布于:2023-01-20 ⋅ 阅读:(387) ⋅ 点赞:(0)

书接上期,这一期我们开始讲解STL容器里面的“维克托”也是最常用的容器之一。

 

文章目录

vector的概述

vector的使用 

vector常用API介绍

vector迭代器常用的成员函数介绍:

vector的构造函数操作:

 vector的常用赋值操作:

 vector的大小容量操作:

vector的插入和删除操作: 

 vector的数据存取操作:

 示例:

vector常用的创建,循环插入,遍历:

vector利用swap收缩空间:

vector的遍历以及二维数组:

使用算法对vector数组进行排序:

vector存放自定义数据类型操作:

vector的概述

向量(Vector)是一个封装了动态大小数组的顺序容器。跟任意其它类型容器一样,它能够存放各种类型的对象。可以简单的认为,向量是一个能够存放任意类型的动态数组。

vector 常被称为向量容器,因为该容器擅长在尾部插入或删除元素,在常量时间内就可以完成,时间复杂度为O(1);而对于在容器头部或者中部插入或删除元素,则花费时间要长一些(移动元素需要耗费时间),时间复杂度为线性阶O(n)

本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。

vector的使用 

在学习STL容器时有众多相对应的接口,和许多算法,因此一定要学会看帮助手册以及文档。下面我将列出vector所有常用的接口,并与之对应给出示例帮助学习和记忆。

使用vector容器时需要添加头文件
#include<vector>

vector常用API介绍

vector迭代器常用的成员函数介绍:

begin()函数:

函数原型:
iterator begin();
const_iterator begin();
功能:返回一个当前vector容器中起始元素的迭代器。

end()函数:

函数原型:
iterator end();
const_iterator end();
功能:返回一个当前vector容器中末尾元素的迭代器。
注意end()指向的是最后一个元素的下一个位置,所以访问最后一个元素

rbegin()函数:

返回指向最后一个元素的反向迭代器

rend()函数:

返回指向第一个元素之前一个位置的反向迭代器。

vector的构造函数操作:


	vector<T> v;//采用模板实现类实现,默认构造函数
	vector(v.begin(), v.end());//将v.begin()到 v.end()区间的元素拷贝给本身
	vector(n, elem);//构造函数,将n个elem元素拷贝给自身
	vector(const vector & vec);//拷贝构造函数
	eg使用第二个构造函数
	vector<int>v(arr, arr + sizeof(arr) / sizeof(int));

示例:

下面我会编写一个打印函数,以供后面所有示例去调用,不会再重复编写。

void Show(vector<int>& v1)
{
	vector<int>::iterator it;
	for (it = v1.begin(); it != v1.end(); ++it)
	{
		cout << *it << " ";
	}
	cout << endl;
}
void test02()
{
	vector<int> v1(5, 100);//用5个100来构造自身,调用第三个拷贝构造
	Show(v1);

	vector<int>v2 = v1;//旧对象给新对象赋值,调用第四个拷贝构造
	Show(v2);

	vector<int>v3 (v1.begin(), v1.end());//区间构造,调用第二个拷贝构造
	Show(v3);
}
int main()
{
    test02();
    return 0;
}

 vector的常用赋值操作:


	assign(begin, end);//将(begin,end)区间的数据拷贝赋值给自身
	assign(n, elem);//将n个elem拷贝赋值给自身
	vector& operator=(const vector & vec);//重载等号赋值运算符
	swap(vec);//将vec与本身元素互换

示例: 

void test02()
{
    vector<int> v5 = { 1,2,3,4,5 }; //列表初始化,注意使用的是花括号
    
    //vector<int> v1(10);默认初始化10个0;等同于vector<int> v1(10,0);
	vector<int> v1(5, 100);//用5个100来构造自身,调用第三个拷贝构造
	Show(v1);

	vector<int>v2 = v1;//旧对象给新对象赋值,调用第四个拷贝构造
	Show(v2);

	vector<int>v3 (v1.begin(), v1.end());//区间构造,调用第二个拷贝构造
	Show(v3);

	vector<int> v4;
	v4 = v3;//使用重载的赋值运算符
	Show(v4);
	v4.assign(5, 10);//给v4赋5个10
	Show(v4);
	v4.assign(v1.begin(), v1.end());//区间赋值
	Show(v4);
}
int main()
{
    test02();
    return 0;
}

 vector的大小容量操作:

 首先清楚:

1.capacity:该值在容器初始化时赋值,指的是容器能够容纳的最大的元素的个数。还不能通过下标等访问,因为此时容器中还没有创建任何对象。

2.size:指的是此时容器中实际的元素个数。可以通过下标访问0-(size-1)范围内的对象。

就比如说一个瓶子的容量是600ml,意思是它最多可以装600ml,而不是说现在瓶子里就有600ml,这里的600ml就相当于capacity;而说现在瓶子的size为300ml,就是说它真的装有300ml。

那么就可以分清楚resize和reserve的区别了:

reserve是设置了capacity的值,比如reserve(20),表示该容器最大容量为20,但此时容器内还没有任何对象,也不能通过下标访问。

resize既分配了空间,也创建了对象,可以通过下标访问。

reserve只修改capacity大小,不修改size大小。

 注:这里空间就是capacity,对象就是容器中的元素size

	vector大小容量操作
	size();//返回容器中元素个数
	empty();//判断容器是否为空
	capacity();//容器的容量
	resize(int num);//更改大小,重新指定容器的长度为num,若容器变长,则以默认值填充新位置
	如果容器变短,则末尾超出容器长度的元素被删除
	resize(int num, elem);//更改大小,重新指定容器的长度为num,若容器变长,则以elem填充新位置
	如果容器变短,则末尾超出容器长度的元素被删除
	reserve(int len);//容器预留len个元素长度,预留位置不初始化,元素不可访问

示例: 

void test02()
{
    vector<int> v1(5, 100);//用5个100来构造自身,调用第三个拷贝构造
	
    vector<int>v3 (v1.begin(), v1.end());//区间构造,调用第二个拷贝构造


	vector<int> v4;
	v4 = v3;//使用重载的赋值运算符
    v3.assign(10,1);//给v3赋10个1
	Show(v3);
	v3.swap(v4);//交换v3,v4的值

	//大小,容量
	cout << "大小:" << v4.size() << "容量:" << v4.capacity() << endl;
	//容器是否为空
	vector<int>v5;
	if (v5.empty())
	{
		cout << "为空" << endl;
	}
	else {
		cout << "非空" << endl;
	}
	
	vector<int>v6(10, 30);
	v6.resize(20);//重置长度为20,过大后补0
	cout << "大小:" << v6.size() << "容量:" << v6.capacity() << endl;
	Show(v6);
	v6.resize(25, 5);//重置长度为25,过大后补5
	cout << "大小:" << v6.size() << "容量:" << v6.capacity() << endl;
	Show(v6);

	vector<int>v7;
	cout << "大小:" << v7.size() << "容量:" << v7.capacity() << endl;
	v7.reserve(100);//预留了100的容量空间
	cout << "大小:" << v7.size() << "容量:" << v7.capacity() << endl;
}
int main()
{
    test02();
    return 0;
}

vector的插入和删除操作: 

insert(const iterator pos, int count, ele);//迭代器指向pos位置插入count个元素ele
	push_back(elem);//尾部插入元素elem
	pop_back();//删除最后一个元素
	erase(const iterator start, const iterator end);//删除迭代器start到end间的元素
	erase(const iterator pos);//删除迭代器指向的元素
	clear();//删除容器中所有元素

示例:

void test02()
{
    vector<int>v8;
	v8.push_back(1); //插入元素
	v8.push_back(2);
	v8.push_back(3);
	v8.pop_back();//尾删
	Show(v8);//1 2 

	vector<int>v9;
	v9.insert(v9.begin(), 5, 2);//使用迭代器插入
	Show(v9);//2 2 2 2 2
	v9.insert(v9.begin()+2,2, 19);
	Show(v9);//2 2 19 19 2 2 2
	v9.insert(v9.end(), 2, 15);
	Show(v9);//2 2 19 19 2 2 2 15 15 
	
	v9.erase(v9.begin(), v9.begin()+3);//删除了前三个元素
	Show(v9);

	v9.clear();//清空内容大小,容量不变
	cout << "大小:" << v9.size() << "容量:" << v9.capacity() << endl;

}
int main()
{
    test02();
    return 0;
}

 vector的数据存取操作:

    at(int index);//返回索引index所指的数据,如果index越界,跑出out of range异常
	operator[];//返回索引idx所指的数据,越界时,直接报错
	front();//返回容器中第一个数据元素
	back();//返回容器中最后一个元素

 示例:

void test02()
{
    vector<int>v8;
	v8.push_back(1); //插入元素
	v8.push_back(2);
	v8.push_back(3);
	cout << "头元素:" << v8.front() << " 尾元素:" << v8.back() << endl;
	//at越界会跑出异常,【】不会抛出异常
	cout << v8.at(1) << "  " << v8[1] << endl;//取元素,下标从0开始
	v8.at(0) = 10;//赋值,更改
	v8[2] = 15;
	Show(v8);//10 2 15

	v8.pop_back();//尾删
	Show(v8);//10 2 
}
int main()
{
    test02();
    return 0;    
}

vector常用的创建,循环插入,遍历:

int main()
{
	vector<int> x{1,2,3,4,5};//初始化x,赋值为1 2 3 4 5 
	//vector<int> x={1,2,3,4,5}等价
	//遍历
	for (auto num : x)
	{
		cout << num << " ";
	}
	cout << endl;
	x.clear();//清空
	// 插入
	for (int i = 0; i < 5; i++)//循环插入1 2 3 4 5
	{
		x.push_back(i+1);
	}
	//遍历
	for (vector<int>::iterator it = x.begin(); it != x.end(); it++)
	{
		cout << (*it) << " ";
	}
	cout << endl;
	//遍历
	for (auto it = x.begin(); it != x.end(); it++)
	{
		cout << (*it) << " ";
	}
	cout << endl;
	//遍历
	for (int j = 0; j < 5; j++)
	{
		cout << x[j] << " ";
	}
	cout << endl;
	vector<int>p(5); //容器开始就有5个元素,它们的默认初始值都为 0。
	for (int j = 0; j < p.size(); j++)
	{
		cout << p[j] << " ";
	}
	cout << endl;
	cout << p.front() << endl;
	cout << p.size() << endl;
}

vector利用swap收缩空间:

在C++标准库容器vector的容量是不会自动的缩减的,也就是说删除元素操作,
其引用、指针、迭代器也会继续有效。
那么当在一个较大的vector中删除了大量的元素之后,
其实际的size比较小,而其capacity比较大,如果对空间比较敏感,
希望vector的容量能够缩小一些,这时可以使用下面的技巧来实现。

void test03()
{
    vector<int>v1;
	v1.reserve(500);
	v1.assign(5, 60);
	cout << "大小:" << v1.size() << "容量:" << v1.capacity() << endl;
	//使用拷贝构造函数创建匿名对象,调用匿名对象的swap函数
    //匿名对象在拷贝函数代码行结束后即被系统回收,实现了空间缩小
	//匿名对象在拷贝时只拷贝了他的有效空间
	vector<int>(v1).swap(v1);

	cout << "大小:" << v1.size() << "容量:" << v1.capacity() << endl;
}
int main()
{
    test03();
    return 0;
}

我们会发现容量仅仅减少为之前的有效容量

vector的遍历以及二维数组:

二维数组其实就是一个数组包着许多一维数组构成,那么vector创建一维动态数组,如果创建二维也只需要"vector包着vector即可"

vector< vector<int> > v(m, vector<int>(n) );是什么意思 

定义了一个vector容器,元素类型为vector<int>,初始化为包含m个vector<int>对象,每个对象都是一个新创立的vector<int>对象的拷贝,而这个新创立的vector<int>对象被初始化为包含n个0。
创建了一个vector< vector<int> > 二维数组 m行n列
void test04()
{
	vector<int>v1(5, 10);
	vector<int>v2(4, 20);
	vector<int>v3(3, 30);
	//定义一个容器 存放v1 v2 v3
	//容器嵌套容器,二维数组
	vector<vector<int>>v;
	v.push_back(v1);//插入
	v.push_back(v2);
	v.push_back(v3);
	//遍历 利用数组方式遍历
	for (int i = 0; i < v.size(); ++i)
	{
		for (int j = 0; j < v[i].size(); ++j)
		{
			cout << v[i][j] << " ";
		}
		cout << endl;
	}
	//利用迭代器的方式遍历
	vector<vector<int>>::iterator it;
	for (it = v.begin(); it!= v.end(); ++it)
	{
		vector<int>::iterator mit;
		for (mit = (*it).begin(); mit != (*it).end(); ++mit)
		{
			cout << *mit << " ";
		}
		cout << endl;
	}
}
int main()
{
    test04();
    return 0;
}

使用算法对vector数组进行排序:

STL提供了大量的算法便于操作,这里我们仅用sort最简单的排序函数演示如何操作,后续将会吧所有常用的算法使用出一期文章。

要了解:

算法主要是由头文件<algorithm><functional> <numeric>组成。
<algorithm>是所有STL头文件中最大的一个,其中常用的功能涉及到比较,交换,查找,遍历,复制,修改,反转,排序,合并等…
<numeric>体积很小,只包括在几个序列容器上进行的简单运算的模板函数.
<functional> 定义了一些模板类,用以声明函数对象。

void test05()
{
	vector<int>v;
	v.push_back(5);//插入
	v.push_back(2);
	v.push_back(3);
	v.push_back(6);
	Show(v);
	//sort算法排序 
	//包含头文件#include<algorithm>算法头文件
	sort(v.begin(), v.end());//默认从小到大排
	Show(v);
	sort(v.begin(), v.end(),greater<int>());//从大到小排 加个规则greater<int>()
	Show(v);
}
int main()
{
    test05();
    return 0;
}

vector存放自定义数据类型操作:

在C++中对于类的使用是常态,因此对于vector操作自定义的数据类型要掌握。

class Person
{
private:
	int num;
	string name;
public:
	Person(){}
	Person(int num1, string name1) :num(num1), name(name1)
	{
		cout << "有参构造" << endl;
	}
	void Setnum(int num1) { this->num = num1; }
	void Setname(string name1) { this->name = name1; }
	int Getnum() {
		return num;
	}
	string Getname()
	{
		return name;
	}

};
void test06()
{
	vector<Person> v;
	v.push_back(Person(12, "lucy"));
	v.push_back(Person(20, "Joe"));
	v.push_back(Person(36, "Tom"));
	vector<Person>::iterator it;
	for (it = v.begin(); it != v.end(); ++it)
	{
		cout << "年龄: " << (*it).Getnum() << "姓名:" << (*it).Getname();
		cout << endl;
	}
    注意:对于自定义容器的排序,必须自己实现排序规则
            或重载自定义数据类型的< 或>
}
int main()
{
    test06();
    return 0;
}

看到这里相信对于vector的操作与使用就有了一定的了解,后续容器的很多使用也与vector近似,这一系列也会持续更新下去,感谢观看!


网站公告

今日签到

点亮在社区的每一天
去签到