引言
亲爱的小伙伴们,欢迎回到我们的STL学习之旅!在上一篇文章中,我们一起探讨了string容器,了解了如何在C++中高效地处理字符串。今天,我们将要进入一个新的篇章,一起来学习STL中的另一个强大容器——vector。vector,即向量容器,是一种可以动态调整大小的数组,它为我们提供了比普通数组更灵活、更便捷的操作方式。无论是存储基本数据类型还是自定义对象,vector都能轻松应对。那么,让我们系好安全带,开始探索vector容器的奇妙世界吧!冲冲冲!!!
STL介绍
STL(standard template library),叫做标准模板库,可以帮我们建立一套数据结构和算法的标准,提高复用性。STL中大量使用了模板技术。
STL中有三大部分:
容器:container,存放数据的地方,STL中实现了很多种数据结构的容器。
算法:algorithm,操作数据的方法,解决问题的方法。
迭代器:iterator,迭代器就是操作容器中数据的指针,它是对原始指针的封装,本质上是一个类模板,同时重载了指针的各种运算符。
vector容器
1.vector的基本概念
vector和数组这种结构很相似,但有区别:
- 1.vector是一个类模板,是对数组的进一步封装。
- 2.vector具有动态拓展的能力,而传统数组是静态的,长度不可变的。
- vector的拓展原理:
当空间不足的时候,vector会重新申请一块更大的空间,然后将旧空间的元素全部拷贝到新空间,再把旧空间释放掉。 - vector涉及到两个概念:
容量:capacity,最多可以容纳元素的个数
大小:size,实际存放元素的个数
2.vector构造函数
- vector v;//采用类模板实现,无参构造,构造了一个空的vector容器,就是v
- vector vec(iterator begin,iterator end);//参数是两个迭代器,两个位置,将另一个vector的[begin,end)区间中的元素拷贝给当前vec对象,完成构造。
- vector v(const vector& vec);//拷贝构造
- vector v(n,ele);//ele是element,即元素。使用n个ele元素完成当前vector对象的构造。
- 代码展示:
void test01()
{
vector<int> v1;//无参构造
for (int i = 0; i < 10; i++)
{
v1.push_back(i);
}
vector<int> v2(v1.begin(), v1.end());//v2根据v1的[begin,end)区间的值完成构造
vector<int> v3(v2);//根据v2拷贝构造v3
vector<int> v4(10, 100);//n个ele来构造
//printVector()是一个遍历打印的函数
printVector(v1);
printVector(v2);
printVector(v3);
printVector(v4);
}
3.vector赋值
- 重载赋值运算符:
vector& operator=(const vector& vec); - 成员函数assign:
assign(beg,end);//将vector[beg,end)区间的元素赋值给当前的vector对象,beg和end是两个迭代器位置
assign(n,ele);//将n个ele元素赋值给当前vector- 代码展示:
//赋值
void test02()
{
vector<int> v1;
for (int i = 0; i < 10; i++)
{
v1.push_back(i);
}
//使用=赋值
vector<int> v2;
v2 = v1;
printVector(v2);
//赋值函数assign
vector<int> v3;
v3.assign(v1.begin() + 2, v1.end() - 2);
printVector(v3);
vector<int> v4;
v4.assign(10, 100);
printVector(v4);
}
4.vector容量和大小
- empty();//判断容器是否为空
- capacity();//返回容器的容量
- size();//返回容器中元素的个数
- resize(int num);//重新指定容器的size为num,如果容器的size变多了,会以默认值填充多余的位置,如果size变少了,会删除多余的元素。
- resize(int num,ele);//重新指定容器的size为num,如果容器的size变多了,会以ele元素填充多余的位置,如果size变少了,会删除多余的元素。
- resize不会改变容量的大小,某些情况下就会存在空间的浪费,这时候可以通过一个缩容的函数来缩小容量shrink_to_fit()
- 代码展示:
//容量和大小
void test03()
{
vector<int> v1;
for (int i = 0; i < 10; i++)
{
v1.push_back(i);
}
cout << v1.empty() << endl;//0
cout << "容量:" << v1.capacity() << endl;//13
cout << "大小:" << v1.size() << endl;//10
//resize重新指定大小
v1.resize(5);
printVector(v1);
v1.resize(10);
printVector(v1);
v1.resize(15, 100);
printVector(v1);
//缩容
cout << "v1的容量:" << v1.capacity() << endl;
v1.resize(5);
cout << "v1的容量:" << v1.capacity() << endl;//可见resize没有改变容量的值,此时存在空间的浪费
v1.shrink_to_fit();
cout << "v1的容量:" << v1.capacity() << endl;//缩容之后,容量变成5,跟size值相等
}
5.vector插入和删除
-
尾部操作:
push_back(ele);//尾部推入一个元素ele
pop_back();//尾部最后一个元素ele被弹出 -
指定位置插入:insert
insert(iterator pos,ele);//迭代器指向位置pos插入元素ele,并且返回新插入元素的位置迭代器。
insert(iterator pos,int n,ele);//迭代器指向位置pos插入n个元素ele,并且返回新插入多个元素的第一个元素的位置迭代器。
insert(iterator pos,beg,end); //在pos位置插入另一个vector[beg,end)区间的数据,返回新数据的位置。 -
删除:erase
erase(iterator pos);//删除迭代器所指向的元素,返回被删除元素的下一个迭代器位置。
erase(iterator beg,iterator end);//删除beg到end之间的元素,不包括end
clear();//清除容器中的所有元素 -
关于迭代器失效的问题:
vector是属于序列式容器,底层是数组,是物理上连续的内存空间。所以,当发生插入或删除操作后会出现元素的移动,这样会导致
迭代器失效,当迭代器失效后,如果继续操作迭代器,就会报错。所以为了避免错误,我们应该在迭代器失效后马上对迭代器进行更新操作,用一个新的有效的迭代器替代原来失效的迭代器。
进一步分析失效的情况:
1.插入操作:insert和push_back
insert:
当容量足够时,没有发生动态拓展时,insert插入操作会导致插入位置后面所有元素的位置发生变化,从而导致迭代器失效
当容量不够时,会发生动态拓展,会导致所有元素的位置发生变化,导致所有迭代器失效。
push_back:
当容量足够时,没有发生动态拓展时,push_back会导致end迭代器失效
当容量不够时,会发生动态拓展,会导致所有元素的位置发生变化,导致所有迭代器失效。
2.删除操作:erase或pop_back
erase:
删除操作不会导致容量不够的问题,但是会导致被删除的当前位置迭代器失效,以及后面所有的迭代器失效。
pop_back:
只会导致end迭代器失效,但不会影响前面的迭代器。
3.insert和erase方法都会返回迭代器,我们只需要用返回的有效迭代更新失效的迭代器即可:
如:it=v.insert(it);
如:it=v.erase(it);
void test04()
{
//尾部操作
vector<int> v1;
v1.push_back(10);
v1.push_back(20);
v1.push_back(30);
v1.push_back(40);
v1.push_back(50);
v1.pop_back();//50
v1.pop_back();//40
printVector(v1);
//insert插入
v1.insert(v1.begin(), 100);
printVector(v1);
v1.insert(v1.begin(), 2, 1000);
printVector(v1);
vector<int> v2 = { 6,7,8,9,0 };
v1.insert(v1.begin(), v2.begin(), v2.begin() + 2);
printVector(v1);
//删除erase
v2.erase(v2.end() - 1);
printVector(v2);
v2.erase(v2.begin() + 1, v2.begin() + 3);
printVector(v2);
v2.clear();
cout << v2.size() << endl;//0
//插入和删除的返回值
cout << "插入和删除的返回值:" << endl;
vector<int> v3 = { 1,2,3,4,5,6 };
vector<int>::iterator it_insert = v3.insert(v3.begin() + 1, 100);
cout << *it_insert << endl;//100
it_insert = v3.insert(v3.begin() + 1, 3, 1000);
cout << *it_insert << endl;//第一个1000
vector<int>::iterator it_erase = v3.erase(v3.begin() + 4);//删除了100
cout << *it_erase << endl;//返回100后面的迭代器,即2的迭代器
it_erase = v3.erase(v3.begin() + 1, v3.begin() + 4);//删除了三个1000
cout << *it_erase << endl;//2
printVector(v3);
}
6.vector数据存取
- operator[int index];//重载了[]运算符,可以通过下标取值
- at(int index);//返回索引index位置的元素
- front();//返回容器中第一个元素
- back();//返回容器中最后一个元素
- 代码展示:
//迭代器失效问题
void test05()
{
vector<int> v;
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(30);
v.push_back(40);
v.push_back(50);
printVector(v);
要求删除所有30的元素
遍历整个数组,如果发现是30,就删掉
//for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
//{
// if (*it==30)
// {
// v.erase(it);//这里会报错,原因是,第一个30被删掉了,它的迭代器就失效了,但此时的循环还继续以这个失效的it进行++,所以会报错
// }
//}
以上问题,需要在循环中,更新迭代器
//for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
//{
// if (*it == 30)
// {
// it=v.erase(it);//这里更新迭代器,才可以继续下去
// }
//}
//printVector(v);
//以上代码也有bug,会跳过第二个30
//应该改成如下:
//for (vector<int>::iterator it = v.begin(); it != v.end();)
//{
// if (*it == 30)
// {
// it = v.erase(it);//此处更新it,已经指向被删除元素的下一个位置,相当于已经++了
// }
// else//只有不是30的时候,由于没有更新迭代器,才需要it++
// {
// it++;
// }
//}
//printVector(v);
//插入操作也会导致迭代器失效
//在30之前插入100
for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
{
if (*it == 30)
{
it = v.insert(it, 100);
it++;
}
}
printVector(v);
}
//数据存取
void test06()
{
vector<int> v1;
for (int i = 0; i < 10; i++)
{
v1.push_back(i);
}
//另外一种获取元素的方式,通过下标取值
for (int i = 0; i < v1.size(); i++)
{
cout << v1[i] << " ";
}
cout << "首元素:" << v1.front() << endl;
cout << "尾元素:" << v1.back() << endl;
}
7.vector互换
- 可以实现两个vector容器元素的互换
- v1.swap(v2);//v1和v2互换元素
- 代码展示:
//互换
void test07()
{
vector<int> v1;
for (int i = 0; i < 10; i++)
{
v1.push_back(i);
}
vector<int> v2 = { 6,6,6,6,6,6 };
v1.swap(v2);
printVector(v1);
printVector(v2);
}
8.vector预留空间
当vector存储的数据量很大时,由于vector的动态数组特性,会导致多次的动态扩展发生,这样会降低效率。
为了避免多次拓展的情况发生,可以在一开始就指定一些预留的空间,其实就是指定了capacity的值。
reserve(int len);//给容器预留len个元素的位置,预留的位置不可访问,因为没有被初始化。
9.算法
这是算法,适用于大部分容器的,不是vector的成员函数
排序:
sort(iterator beg,iterator end);//对区间内的元素进行排序,默认升序,从小到大
sort(iterator beg,iterator end,func);//对区间内的元素进行排序,使用func指定排序方式
反转:
reverse(iterator beg,iterator end);//对区间内元素反转倒置
复制:
copy(源容器起始位置,源容器结束位置,目标容器的起始位置);//将源容器的区间元素复制到目标容器指定位置,注意是替换,新复制进去的值替换了原来位置的值
查找:
find(iterator beg,iterator end,ele);//在区间内查找元素ele,找到返回所在位置的迭代器,找不到返回end迭代器,注意这个end指的是区间中指定的end位置,不一定是整个容器的end()。
遍历:
for_each(iterator beg,iterator end,func);//遍历容器区间内的元素,func是个函数或者仿函数,指明遍历时对元素的操作。
- 代码展示:
//常用算法
//定义排序器函数,实现降序
bool cap(int a, int b)
{
return a > b;
}
//定义用于for_each算法操作的函数
void myPrint(int val)
{
val++;
cout << val << " ";
}
void test09()
{
//排序sort,默认升序
vector<int> v1 = { 28,42,5,31,65,32,55,83,12,19 };
sort(v1.begin(), v1.end());
printVector(v1);
//通过func函数实现降序排序,这个func函数叫做排序器函数,是sort的第三个参数
sort(v1.begin(), v1.end(), cap);
printVector(v1);
//反转
reverse(v1.begin(), v1.begin() + 5);
printVector(v1);
//复制
vector<int> v2;
v2.assign(10, 100);
//将v1的前五个值,复制到v2的前五个位置
copy(v1.begin(), v1.begin() + 5, v2.begin());
printVector(v2);
//查找
vector<int>::iterator it = find(v1.begin(), v1.begin() + 5, 83);
if (it == v1.begin() + 5)
{
cout << "没找到" << endl;
}
else
{
cout << "找到了,它的值是:" << *it << ",它的下标是:" << it - v1.begin() << endl;
}
//遍历
for_each(v1.begin(), v1.end(), myPrint);
}
10.总结
vector是单端数组,是单端开口的,这种数据结构,决定了它在尾部的插入和删除操作效率是比较高的,因为不会导致其他元素的移动。
但是,在其他位置插入或者删除的时候,效率较低,因为会导致其他元素移动。
vector由于是连续的物理空间存储的结构,底层是数组,所以可以使用下标对元素进行访问,但比数组好用,多了动态拓展。
这种结构,做遍历查询的时候,效率很高。
11.小练习
- 1.请大家使用三种不同的方式对一个vector进行遍历打印操作
void test10()
{
vector<int> v = { 1,2,3,4,5,6 };
for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
{
cout << *it << " ";
}
cout << endl;
for (int i = 0; i < v.size(); i++)
{
cout << v[i] << " ";
}
cout << endl;
//while循环或者for_each算法
vector<int>::iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
it++;
}
cout << endl;
}
- 2.使用vector容器存储多个Person(m_Name,m_Age)对象,然后输出每个对象的属性
class Person
{
string m_Name;
int m_Age;
public:
Person(string name, int age)
{
m_Name = name;
m_Age = age;
}
void show() { cout << "name:" << m_Name << ",age:" << m_Age << endl; }
};
void test11()
{
vector<Person> v;
Person p1("tom", 10);
Person p2("lucy", 20);
Person p3("john", 14);
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
//v = { p1,p2,p3 };
for (int i = 0; i < v.size(); i++)
{
v[i].show();
}
}
- 3.实现一个嵌套型的vector,一个vector中存放的元素还是vector,具体来说是vector,请输出每个vector中的int元素
void test12()
{
vector<vector<int>> v;
vector<int> v1 = { 1,2,3 };
vector<int> v2 = { 4,5,6 };
vector<int> v3 = { 7,8,9 };
v = { v1,v2,v3 };
//二维数组,需要两层循环
for (int i = 0; i < v.size(); i++)
{
for (int j = 0; j < v[i].size(); j++)
{
cout << v[i][j] << " ";
}
cout << endl;
}
for (vector<vector<int>>::iterator it = v.begin(); it != v.end(); it++)
{
for (vector<int>::iterator vit = (*it).begin(); vit != (*it).end(); vit++)
{
cout << *vit << " ";
}
cout << endl;
}
}
结语
通过本篇文章的学习,我们共同掌握了vector容器的基本用法,包括它的构造、添加、删除、访问元素等功能。vector的灵活性和便捷性使其成为C++程序设计中不可或缺的工具之一。
当然,vector还有更多高级特性等待我们去挖掘,比如迭代器、算法操作等。在接下来的学习中,我们将继续深入探讨这些内容,让大家能够更加熟练地运用vector解决实际问题。如果你在学习和使用vector的过程中有任何疑问,欢迎留言讨论。让我们一起在C++的编程世界中不断进步,下期再见!
都学到这里啦!给厉害的自己鼓鼓掌吧!!!👏👏👏👏👏👏👏👏