书接上期,这一期我们开始讲解STL容器里面的“维克托”也是最常用的容器之一。
文章目录
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近似,这一系列也会持续更新下去,感谢观看!