【C++】vector容器的基本使用
心怀花木 2024-10-01 13:35:13 阅读 53
一、vector是什么
vector是STL第一个正式的容器,它的底层其实就是动态数组,插入数据时当容量满了会自动扩容,它和string差不多,不同的之处之一在于vector本身是一个模板,它这个容器中可以存放各种各样的类型的数据,而string已经是模板实例化之后的结果。
vector类模板的第一个参数是T,其实就是你要放进容器中数据的类型,第二个参数我们暂且不管,它是一个空间配置器,主要是提高效率的,我们在实例化时暂且不传第二个参数,用它的默认缺省值,我们暂且只传第一个参数即可。
在本篇中涉及到allocator空间配置器的我们暂且不用管,可以先忽略。
vector大多数接口和string的功能用法上没有什么太大的区别,这篇文章不会细讲,所以建议大家先去看一下这篇文章 -> string类的基本实现
二、基本使用
1、构造函数
C++98版本下有4个构造函数,我们这里只说C++98,不谈论其它版本。
我们来看一下它的使用:
{
vector<int> v1; //(1)默认构造
vector<int> v2(10,1); //(2)带参构造,int类型的10个1
vector<int> v3(v2.begin(),v2.end()); //(3)迭代器区间构造
vector<int> v4(v3); //(4)拷贝构造
}
带有空间配置器的构造函数,我们用它的缺省值。
value_type就是模板的第一个参数T,即容器中存放数据的类型。
通过调试,我们可以看到各个容器中的内容:
2、析构函数
析构函数就不用多说了,我们创建容器添加数据,要在堆上开辟动态空间,析构函数就是要来对这些开辟的空间进行释放的,从而销毁容器对象。编译器会自动调用析构函数,我们可以不用单独处理。
我们可以通过调试简单演示一下:
程序结束前:
程序结束后:
程序结束自动调用析构,销毁容器对象。
3、赋值重载
它的作用就是将 x 中的所有元素复制到容器中。
我们用代码来理解一下:
{
vector<int> v1(2,6);
vector<int> v2(3,1);
v2 = v1;
}
调试结果:
赋值前:
赋值后:
从这两张图可以看出,当v2的size大小取决于v1,如果v2在赋值前容量比v1大,则赋值后保持不变,否则会扩容,保证能存放完v1的数据。
4、重载[]
因为vector的底层是动态数组,所以它也支持用[]来访问指定下标的元素。
{
vector<int> v(6, 6);
cout << v[0] << endl;
cout << v[2] << endl;
}
在主函数中调用test_vector3()结果如下:
如果越界访问就会报错。
这里也有3种遍历容器的方法,和string差不多一样:
{
vector<int> v1;
vector<int> v2(10, 1);
vector<int> v3(++v2.begin(), --v2.end());
//1、重载[]
for (size_t i = 0; i < v3.size(); i++)
{
cout << v3[i] << " ";
}
cout << endl;
//2、迭代器
vector<int>::iterator it = v3.begin();
while (it != v3.end())
{
cout << *it << " ";
++it;
}
cout << endl;
//3、范围for
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
}
在主函数中调用test_vector4()结果如下:
这3种迭代方法,我们在string篇幅都已详细讲解了,大家如果不懂,可以看一下那篇文章。
5、扩容规律
我们可以看一下vector在添加元素时,自动扩容的规律:
<code>void TestVectorExpand()
{
size_t sz;
vector<int> v;
sz = v.capacity();
cout << "capacity changed: " << sz << '\n';
cout << "making v grow:\n";
for (int i = 0; i < 100; ++i)
{
v.push_back(i); //尾插
if (sz != v.capacity())
{
sz = v.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
在主函数中调用TestVectorExpand()结果如下:
不难发现,它是从0开始扩的,这与string是不同的,它是严格的1.5倍扩容。
6、成员函数
成员函数中大多和string用法和功能一样,这里只说一些特殊的。
(1)reserve()
它的功能是也是预留容量的,也就是改变capacity的大小,它可以避免频繁扩容。
size_type是一个无符号整形,它和string中的reserve()成员函数相似,但有一点不同,我们先往下看,假设参数是n(就是改变后的容量大小),分3种情况:
1、n < size
明确不会缩容。(这是和string不同的)
也不会改变size的大小,就是不会破坏原有内容。
2、size < n < capacity
明确不会缩容。(这是和string不同的)
3、n > capacity
会扩容,至少扩到n,也可能更多,这是不确定的。
我们写一段代码验证一下:
{
vector<int> v(10, 1);
cout << v.size() << endl;
cout << v.capacity() << endl;
//1、 n > capacity
v.reserve(20);
cout << v.size() << endl;
cout << v.capacity() << endl;
//2、size < n < capacity
v.reserve(15);
cout << v.size() << endl;
cout << v.capacity() << endl;
//3、n < size
v.reserve(5);
cout << v.size() << endl;
cout << v.capacity() << endl;
}
在主函数中调用test_vector5()结果如下:
根据结果显示,当n < capacity时,它是不会缩容的,这是明确的。
(2)resize()
它是将size设置为n,也分3种情况:
1、n < size
size的大小会变为n,其余的size - n个元素被删除(摧毁),但capacity通常不变。
2、size < n < capacity
size的大小会变为n,插入n - capacity个数据,如果不给第二个参数,那就用给的缺省值来初始化这n - capacity个数据,如果value_type是自定义类型,就调用它的默认构造,如果想自己初始化这n - capacity个数据,那么就手动给第二个参数赋值。
3、n > capacity
会扩容,至少扩到n,也可能更多,这是不确定的。在vs下通常会扩的更多一些。
对于前两点,缩不缩容不一定,这个需要看平台的处理。
我们写一段代码验证一下:
{
vector<int> v(3, 1);
v.reserve(8); //提前预留8字节空间
cout << v.size() << endl;
cout << v.capacity() << endl;
//1、n < size
v.resize(1);
cout << v.size() << endl;
cout << v.capacity() << endl;
//2、size < n < capacity
v.resize(5, 3); //多余的n - size个数的数据初始化为3
cout << v.size() << endl;
cout << v.capacity() << endl;
//3、size > capacity
v.resize(10,100);//多余的n - size个数的数据初始化为100
cout << v.size() << endl;
cout << v.capacity() << endl;
}
在主函数中调用test_vector6()结果如下:
大家对比上面的3点进行理解。
(3)insert()
这里的insert比string当中的insert简洁了许多。
它在这不支持下标了,只支持迭代器。
{
vector<int> v2(2, 0);
vector<int> v1(5, 1);
for (auto e : v1)
cout << e << " ";
cout << endl;
v1.insert(v1.begin(), 0); //(1)在v1头部位置插入0
for (auto e : v1)
cout << e << " ";
cout << endl;
v1.insert(v1.begin() + 3, 3, 100); //(2)在v1下标为3的位置上插入3个100
for (auto e : v1)
cout << e << " ";
cout << endl;
v1.insert(v1.end(), v2.begin(), v2.end());//(3)在v1的末尾,插入一段迭代区间
for (auto e : v1)
cout << e << " ";
cout << endl;
}
在主函数中调用test_vector7()结果如下:
它不直接支持下标,但间接却是支持的,因为用迭代器就可以实现下标的问题,假设你要在下标为3的位置插入数据,那迭代器 v.begin() + 3就可以实现。这里没有用下标更多的原因是和后面的容器进行兼容,像list,它的底层不是动态数组,用下标访问就是不合适的。所以我们在容器这一部分,统一都用迭代器。
(4)erase()
同时,它也是只支持用迭代器来进行相应位置数据删除。
7、其他
vector不支持流插入和流提取,因为它的打印形式是多样的,不像string那样是固定的,遇到'\0'就终止打印,vector不支持流插入和流提取方便了我们对打印形式的控制,更自由和灵活。
vector底层是动态数组,string底层也是动态数组,那vector<char> 可以等同于string吗?
答案是不能。
首先,string定义的对象后面默认有'\0',vector<char>没有,其次,string定义的对象可以用字符串进行初始化,vector<char>不能,接着,string有很多针对字符串具有特定功能的接口,vector却没有,最后,string可以进行一些接口的传参,如果换用vector<char>则会麻烦许多。
所以vector<char> 是不可以取代string的。
三、结语
以上就是本篇的全部内容了,主要讲了vector的基本使用,希望大家有所收获,祝大家天天开心!
上一篇: 一文弄懂 | YOLOv8网络结构解读 、yolov8.yaml配置文件详细解读与说明、模型训练参数详细解析 | 通俗易懂!入门必看系列!
下一篇: ⭐️开发语言怎么选? 别急!深度了解【2024年全球排行榜TOP20编程语言榜单排名的特点、难易程度、跨平台性、适用领域】是什么,能干嘛?重点不是语言的新旧,是否热门语言,而是找到适合自己的技术栈!
本文标签
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。