在昨天学完string容器后,想必大家对容器都有了进一步的认识。那么今天,我们就一起开始STL中第二个常用容器——vector容器的学习吧~
vector容器——基本概念
之前在STL初识中讲到过,vector容器本质上是一个数组。那么vector容器和普通数组会有什么区别吗?有的兄弟,有的。它们的不同之处在于数组是静态的,一旦分配了内存,就不能在进行扩展了,而vector容器可以动态扩展。也就是说,若vector容器中存储的数据超出了它原先所被分配的空间的话,那么vector容器会自动地将原先所储存的数据进行拷贝,并在内存中找寻一块更大的空间进行数据存储,最终将原空间进行释放
vector容器——构造函数
大家还记得如何向vector容器中插入数据吗?是不是要调用一个叫push_back的函数呀?这个插入数据的方法叫做“尾插法”。那大家会不会有这样一个疑问:既然有尾插法,那是不是还有一个头插法push_head函数呢?没有的兄弟,没有。在vector容器中只有尾插法,没有所谓的头插法。那这又是为什么呢?这其实是因为vector容器又称为单端数组,可以抽象地理解为一个“左闭右开”的区间,只能在其后端进行数据的插入噢~
- 先写一个打印函数,方便后续调用
void printVector(vector<int>&v)
{
for (vector<int>::iterator it = v.begin();it != v.end();it++)
{
cout << *it << " ";
}
cout << endl;
}
构造代码示例:
void test01()
{
//默认构造 无参函数
vector<int>v1;
for (int i = 0;i < 10;i++)
{
//使用尾插法进行数据存储
v1.push_back(i);
}
printVector(v1);
//输出:0 1 2 3 4 5 6 7 8 9
1、通过区间的方式进行构造
vector<int>v2(v1.begin(),v1.end());
//特别强调:因为v.end()是指向vector容器中最后一个元素的下一个空间位置
//所以用(v1.begin(),v1.end())进行构造实质上是
//将v[begin(),end())区间中的元素拷贝给v2本身(左闭右开区间)
printVector(v2);
//输出:0 1 2 3 4 5 6 7 8 9
2、用n个elem方式进行构造
//参数1:插入元素的个数 参数2: 插入元素的具体数值
vector<int>v3(10, 100);
printVector(v3);
//输出:100 100 100 100 100 100 100 100 100 100
3、拷贝构造(常用)
vector<int>v4(v3);
printVector(v4);
//输出:100 100 100 100 100 100 100 100 100 100
}
总结:
- vector容器作为单端数组,只可以在其尾部进行数据的插入\删除操作
- vector容器可以动态扩展,所谓的动态扩展并不是在原有空间的基础上进行扩展,而是将所有数据进行拷贝并在内存中找寻更大的空间进行存入,同时释放旧空间
- vector容器的多种构造方式没有可比性,能够灵活使用即可
vector容器——赋值操作
对vector容器进行赋值主要有以下三种方法
- 重载等号操作符:
void test01()
{
vector<int>v1;
for (int i = 0;i < 10;i++)
{
v1.push_back(i);
}
printVector(v1);
vector<int>v2;
//operator= 赋值
v2 = v1;
printVector(v2);
//输出:0 1 2 3 4 5 6 7 8 9
- 利用assign函数将[beg,end)区间中的数据进行拷贝赋值:
vector<int>v3;
//assign函数赋值
v3.assign(v2.begin(), v2.end());
printVector(v3);
//输出:0 1 2 3 4 5 6 7 8 9
- 利用assign函数将n个elem元素进行拷贝赋值:
vector<int>v4;
//assign函数进行赋值
//参数1:插入元素的个数 参数2: 插入元素的具体数值
v4.assign(10, 100);
printVector(v4);
//输出:100 100 100 100 100 100 100 100 100 100
总结:哪种喜欢用哪个即可
vector容器——容量和大小
在vector容器中,它给我们提供了一系列的函数来让我们进行查询\指定容器的容量和大小,稍加学习便可掌握~
示例代码:
void test01()
{
vector<int>v1;
for (int i = 0;i < 10;i++)
{
v1.push_back(i);
}
printVector(v1);
1、利用empty函数来查询容器是否为空
if (v1.empty()) //为真
{
//为真返回:
cout << "v1为空" << endl;
}
else
{
//为假返回:
cout << "v1不为空" << endl;
2、利用capacity函数查询容器容量
cout << "v1的容量为:" << v1.capacity() << endl;
//输出:v1的容量为:13
3、利用size函数查询容器大小
cout << "v1的大小为:" << v1.size() << endl;
//输出:v1的大小为:10
}
4、利用resize函数重新指定大小
//如果指定的比原先的长,则默认用0来填充
v1.resize(15);
printVector(v1);
//输出:0 1 2 3 4 5 6 7 8 9 0 0 0 0 0
//利用重载的版本可以指定默认的填充值,也就是参数2
v1.resize(15,100);
printVector(v1);
//输出:0 1 2 3 4 5 6 7 8 9 100 100 100 100 100
//如果指定的比原先的短,那么超出的部分会被删除
v1.resize(5);
printVector(v1);
//输出:0 1 2 3 4
}
总结:其实只要认识这几个函数的英文单词便能理解并掌握其用法~
vector容器——插入和删除
不多说了,自己看吧,和前面的内容基本上别无二致,就是换了个单词从而调用不同的函数而已,都是容器内部封装好了的~
示例代码:
void test01()
{
vector<int>v1;
1、利用push_back函数进行尾插
v1.push_back(10);
v1.push_back(20);
v1.push_back(30);
v1.push_back(40);
v1.push_back(50);
printVector(v1);
//输出:10 20 30 40 50
2、利用pop_back函数进行尾删
v1.pop_back();
printVector(v1);
//输出:10 20 30 40
3、利用insert函数进行插入
//参数1:迭代器 参数2:需要插入的数据
v1.insert(v1.begin(), 100);
printVector(v1);
//输出:100 10 20 30 40
//insert函数重载版本
//参数1:迭代器 参数2:所需插入元素的个数 参数3:需要插入的数据
v1.insert(v1.begin(), 2, 1000);
printVector(v1);
//输出:1000 1000 100 10 20 30 40
4、利用erase函数进行删除
//参数1:迭代器
v1.erase(v1.begin());
//只删除vector容器首位第一个元素
printVector(v1);
//输出:1000 100 10 20 30 40
//erase函数重载版本
//此时相当于清空容器
v1.erase(v1.begin(),v1.end());
printVector(v1);
//输出:= cout << endl;
//输出空容器相当于换行
5、利用clear函数进行清空
//相当于v1.erase(v1.begin(),v1.end());
v1.clear();
printVector(v1);
//输出:= cout << endl;
//输出空容器相当于换行
}
总结:记住就行~
vector容器——数据存取
示例代码:
void test01()
{
vector<int>v1;
for (int i = 0;i < 10;i++)
{
v1.push_back(i);
}
1、利用[]的方式访问数组中的元素
for (int i = 0;i < v1.size();i++)
{
cout << v1[i] << " ";
}
cout << endl;
//输出:0 1 2 3 4 5 6 7 8 9
2、利用at的方式访问数组中的元素
for (int i = 0;i < v1.size();i++)
{
cout << v1.at(i) << " ";
}
cout << endl;
//输出:0 1 2 3 4 5 6 7 8 9
3、利用front函数获取数组中第一个元素
cout << "第一个元素为:" << v1.front() << endl;
//输出:第一个元素为:0
4、利用back函数获取数组中最后一个元素
cout << "最后一个元素为:" << v1.back() << endl;
//输出:最后一个元素为:9
}
总结:记住就行~
vector容器——互换容器
互换容器是将两个容器内的元素进行互换
示例代码:
void test01()
{
vector<int>v1;
for (int i = 0;i < 10;i++)
{
v1.push_back(i);
}
cout << "交换前:" << endl;
printVector(v1);
//输出:0 1 2 3 4 5 6 7 8 9
vector<int>v2;
for (int i = 10;i > 0;i--)
{
v2.push_back(i);
}
printVector(v2);
//输出:10 9 8 7 6 5 4 3 2 1
cout << "交换后:" << endl;
1、利用swap函数进行两个容器内元素交换
v1.swap(v2);
printVector(v1);
printVector(v2);
//输出:10 9 8 7 6 5 4 3 2 1
//输出:0 1 2 3 4 5 6 7 8 9
}
交换函数有重要的实际运用,巧用swap交换函数可以帮助我们极大地收缩内存空间
示例代码:
void test02()
{
vector<int>v;
for (int i = 0;i < 100000;i++)
{
v.push_back(i);
}
cout << "v的容量为:" << v.capacity() << endl;
cout << "v的大小为:" << v.size() << endl;
//输出:v的容量为:138255
//输出:v的大小为:100000
2、利用resize函数重新指定大小
v.resize(3);
cout << "v的容量为:" << v.capacity() << endl;
cout << "v的大小为:" << v.size() << endl;
//输出:v的容量为:138255
//输出:v的大小为:3
3、巧用swap收缩内存
vector<int>(v).swap(v);
cout << "v的容量为:" << v.capacity() << endl;
cout << "v的大小为:" << v.size() << endl;
//输出:v的容量为:3
//输出:v的大小为:3
}
以上这段代码中的
vector<int>(v).swap(v);
估计有很多人没有看懂,别急,看我对其的拆分讲解你就懂了~
先把这段代码看成由两部分组成:
- 第一部分:
vector<int>(v)**
创建一个匿名临时对象v,通过拷贝构造函数将原容器v中的元素复制到新对象中。注意:新对象的容量(capacity)会等于其大小(size),即仅分配刚好容纳当前元素的内存
- 第二部分:
.swap(v)
交换匿名对象与原容器v的内部数据指针
此时:
- 原容器v指向匿名对象的小容量内存
- 匿名对象指向原容器的大容量内存
最后:
- 匿名对象销毁:匿名对象是临时变量,代码执行完毕后会自动析构,释放原容器的大容量内存,完成内存收缩
怎么样?看完后是否感觉到恍然大悟呢?如果仍然看的不太懂,那只能说明之前学过的知识不太扎实,建议回头复习一下噢~
总结:swap函数可以使两个容器互换,在实际应用中可以达到收缩内存的效果
vector容器——预留空间
预留空间是为了减少vector容器在动态扩展时的扩展次数
示例代码:
void test01()
{
vector<int>v;
for (int i = 0;i < 100000;i++)
{
v.push_back(i);
}
printVector(v);
}
在以上代码中,若要在vector容器中插入十万量级的数时,vector容器的动态扩展会扩展多少次?通过以下代码进行查看
示例代码:
void test01()
{
vector<int>v;
//定义变量num,统计开辟的次数
int num = 0;
//先让指针变量指为空
int* p = NULL;
for (int i = 0;i < 100000;i++)
{
v.push_back(i);
//判断容器中第一个元素的首地址的值是否与指针变量p相等
if (p != &v[0])
{
//若不相等则将容器中第一个元素的首地址的值(再)赋值给指针变量p
//不相等说明(再次)进行了动态分配内存
p = &v[0];
//记录动态分配内存的次数
num++;
}
}
cout << "num = " << num << endl;
//输出:num = 30
若不进行事先预留空间,在vector容器中插入十万量级的数需要至少30次的动态内存分配,效率未免也太低下了吧。为进一步提升效率,且在我们提前知道数据量的情况下,不妨来学习以下预留空间的操作~
示例代码:
void test01()
{
vector<int>v;
1、利用reserve函数来预留空间
//参数1;预留空间的大小
v.reserve(100000);
//定义变量num,统计开辟的次数
int num = 0;
//先让指针变量指为空
int* p = NULL;
for (int i = 0;i < 100000;i++)
{
v.push_back(i);
//判断容器中第一个元素的首地址的值是否与指针变量p相等
if (p != &v[0])
{
//若不相等则将容器中第一个元素的首地址的值(再)赋值给指针变量p
//不相等说明(再次)进行了动态分配内存
p = &v[0];
//记录动态分配内存的次数
num++;
}
}
cout << "num = " << num << endl;
//输出:num = 1
}
总结:通过reserve函数进行事先预留空间,可以防止无意义的动态扩展并提高效率。此时,vector容器中动态分配内存的次数变成了1
vector容器的学习算是告一段落了,总的来说知识点都较为简单,大多数只需要稍加理解并记忆就能掌握各种操作所对应函数的用法。唯一需要理解的便是容器互换swap函数收缩内存空间的实际运用,着重理解匿名临时对象v的性质和作用,以及最后会进行析构从而释放大量内存空间的关键操作
通过对vector容器的学习,我认为在实际项目开发中,应重点关注vector容器的内存增长机制与高效回收策略,并结合reserve函数进行空间的预分配从而避免反复扩容,最终通过swap函数及匿名对象实现精准内存回收。后续是否还有更高效的方法去提升容器的操作效率呢?那就等待着我去进一步地学习了