C++初阶-vector的使用
目录
1.vector的简单解释
2.vector的结构
3.vector的成员函数
3.1四个默认成员函数
3.1.1构造函数(含拷贝构造函数)的使用
3.1.2=运算符的重载函数的使用
3.2迭代器部分函数的使用
3.3容量部分函数的用法
3.4元素访问
vector::data
功能说明
示例代码
注意事项
3.5修饰符
3.5.1vector::emplace
基本语法
参数说明
返回值
核心特点
1. 就地构造(In-Place Construction)
2. 比 insert 更高效的情况
3. 迭代器失效问题
示例代码
与 insert 的对比
示例对比
时间复杂度
注意事项
总结
3.5.2vector::emplace_back
3.6分配器
3.7非成员函数的重载
4.vector与string的区别
std::find
5.总结
1.vector的简单解释
vector翻译过来时矢量、向量的意思,就是我们在数据结构中学的顺序表,但是这个顺序表是用模板实现的,所以需要学习该部分内容需要先了解一部分C++模板的知识。(可以看我之前的博客)
2.vector的结构
在C++官网中搜索vector,那么最终会有这个结果。
我们可以通过翻译来得知它的基本知识:
以下是两个模板类型的参数的解释:
在模板部分,我们知道,类模板需要我们显式传递它的类型,但是第二个模板参数好像和我们的缺省值一样,这个东西的意思就是内存池/空间适配器。这个涉及到很多其他的知识(C++中STL六大组件中的空间适配器):
一般我们不传递第二个参数,所以我们暂时不用管第二个参数。
下面还有每个类型的参数的讲解:
这个英文的版本我们可能会看不懂,所以我们用翻译插件试一下结果:
这就是各个参数类型的解释,也就是说,它们都是在vector中用typedef定义后的结果,这样定义后虽然变量名比较长了,但是也方便了理解它的更多知识。这个详细的用法会在vector的成员函数的介绍里面讲解。
3.vector的成员函数
这一部分是这一篇的主体内容,我将讲解一些比较重要的成员函数的用法,不过一些用得不多的函数我不会做过多讲解。其中有很多与string类的成员函数相似的地方,所以我们会通过二者的对比来进行讲解。
3.1四个默认成员函数
这四个默认成员函数分别为:构造函数、拷贝构造函数析构函数以及赋值运算符重载函数。这些函数我们见过很多次了。这里的析构函数就不进行讲解了,它不能显式调用,只讲解其他三个默认成员函数。
3.1.1构造函数(含拷贝构造函数)的使用
第一个是无参形式的初始化,我们可以通过调试来找它的具体初始化的值(参数alloc我们可以视作没有,以后看的时候基本上都是这样),第二个函数是用n个val来进行初始化,相当于我们在string类学到的resize函数,第三个函数是用一段迭代器的区间进行初始化,第四个函数是拷贝构造函数,用一个已经创建的对象进行初始化,用以下代码进行讲解:
#define _CRT_SECURE_NO_WARNINGS 1
#include<vector>
#include<iostream>
using namespace std;
//构造函数的使用举例
int main()
{//1vector<int> v1;//2//用10个1来进行初始化vector<int> v2(10, 1);//3//用v2的一段区间进行初始化vector<int> v3(v2.begin() + 2, v2.end() - 1);//4//用已经实例化的对象进行初始化vector<int> v4(v3);return 0;
}
那么调试有:
这就是vector构造函数的四种形式了,我们只要加以理解即可。
3.1.2=运算符的重载函数的使用
这个函数的使用很简单,就是直接用=赋值即可,我们可以通过v2=v1的方式来完成该操作,也可以v2.operator=(v1)的方式进行赋值,用以下函数进行测试:
#define _CRT_SECURE_NO_WARNINGS 1
#include<vector>
#include<iostream>
using namespace std;
//operator=运算符的使用
int main()
{vector<int> v1, v2;vector<int>v3(3, 4);//第一种使用v1 = v3;//第二种使用v2.operator=(v3);return 0;
}
那么运行结果为:
3.2迭代器部分函数的使用
这个之前在string部分讲解过一些,begin返回普通迭代器开始位置迭代器,end返回普通迭代器结束位置迭代器;而rbegin则返回反向迭代器结束位置的反向迭代器,rend则返回反向迭代器开始位置的反向迭代器;cbegin返回const迭代器开始位置的const迭代器……
一般我们是用begin和end,所以我把每个函数的用法在这里演示一下即可:
#define _CRT_SECURE_NO_WARNINGS 1
#include<vector>
#include<iostream>
using namespace std;
//迭代器部分的成员函数使用
int main()
{//这个构造函数是在C++11实现的,相当于把一个数组赋值给它vector<int> v = { 1,2,3,4,5,6 };//1、2vector<int>::iterator i1 = v.begin();while (i1 != v.end()){cout << *i1 << " ";++i1;}cout << endl;//3、4vector<int>::reverse_iterator i2 = v.rbegin();while (i2 != v.rend()){cout << *i2 << " ";++i2;}cout << endl;//5、6vector<int>::const_iterator i3 = v.cbegin();while (i3 != v.cend()){cout << *i3 << " ";++i3;}cout << endl;//7、8vector<int>::const_reverse_iterator i4 = v.crbegin();while (i4 != v.crend()){cout << *i4 << " ";++i4;}cout << endl;
}
那么运行结果如下:
这些函数中我们先现阶段用得最多的还是普通迭代器,所以我们现在只需要重点了解掌握begin()、end()的用法即可。
3.3容量部分函数的用法
这是vector中的成员函数:
而这是string的成员函数:
我们通过对比发现:vector中没有length和clear函数,但是其实vector中有的,只不过在另外一个地方:
这个modifiers是修饰符的意思,也就是说,vector还是有clear函数的,只是没被我们发现而已。
这些函数的用法和之前的string类的用法一样,如果想要了解具体用法,可以对比我之前发过的博客:string类的讲解1.这里就不进行演示了。
3.4元素访问
在vector中有这几个成员函数:
而在string中有这几个成员函数:
也就是说vector中新增加了一个函数:data,我们来了解该函数的用法:
vector::data
通过我们在之前的那个参数表知道:value_type的意思是第一个模板参数T,也就是说它可以返回的是个指针,其中我们还是不了解它意思,那就从deepseek来寻求答案吧!
功能说明
-
返回一个指向 vector 内部用于存储元素的内存数组的指针
-
该指针保证范围
[data(), data() + size())
始终是有效范围 -
如果 vector 为空,返回的指针可能是也可能不是空指针
-
在与需要原始数组的 C 风格 API 交互时特别有用
示例代码
#include <iostream>
#include <vector>int main()
{std::vector<int> v = {1, 2, 3, 4, 5};// 获取底层数组的指针int* p = v.data();// 通过指针修改元素p[2] = 10;// 打印所有元素for (size_t i = 0; i < v.size(); ++i) {std::cout << p[i] << ' ';}// 输出: 1 2 10 4 5// 传递给C风格函数// some_c_function(v.data(), v.size());
}
注意事项
-
从 C++11 开始提供此功能
-
任何修改 vector 容量的操作(如当 size 等于 capacity 时调用
push_back
)都可能使该指针失效 -
比使用
&v[0]
更安全(后者在 vector 为空时会导致未定义行为) -
与
array::data()
类似,但适用于动态数组
这个函数在需要将 vector 数据传递给传统 C 函数时特别有用,或者在需要直接内存访问以提高性能时非常实用。
以上是deepseek得到的结果我,我们了解后自己来用吧:
#define _CRT_SECURE_NO_WARNINGS 1
#include<vector>
#include<iostream>
using namespace std;
//vector::data的使用
int main()
{vector<int> v1(5, 3);int* p = v1.data();p[3] = 4;p[0] = 2;for (const auto& e : v1){cout << e << " ";}return 0;
}
那么运行结果为:
这就是基本用法,虽然现在看来作用不大,但是之后总有用处的!
3.5修饰符
以下是vector的修饰符中的成员函数:
而在string中修饰符的分类的成员函数有 以下几个:
这里我们发现vector中有了emplace这个函数,还有emplace_back这个成员函数,这就是二者的不同,我们来分析一下两个函数:
3.5.1vector::emplace
通过这个声明我们就看 不出来什么东西,所以这个东西涉及的还是比较复杂的,因为这个函数是从C++11才开始使用的,所以很多东西是不知道的,比如&&这个符号是什么。这个C++11会进行详细的解释,而在现在我们只要了解它的基本用法即可,这里我也不是太懂的,可以通过deepseek来寻求结果:
vector::emplace
是 C++ std::vector
的成员函数,用于在指定位置就地构造(in-place construction)一个新元素。它是 C++11 引入的高效方法,在某些情况下比 insert
更优。
基本语法
template <class... Args>
iterator emplace(const_iterator position, Args&&... args);
参数说明
-
position
:插入位置的迭代器(新元素会插入到该位置之前) -
args...
:传递给元素构造函数的参数(支持变长模板参数)
返回值
返回指向新插入元素的迭代器。
核心特点
1. 就地构造(In-Place Construction)
emplace
直接在 vector
的内存空间中构造对象,避免了临时对象的创建和拷贝/移动,提高了效率。
2. 比 insert
更高效的情况
-
当元素不可拷贝或不可移动时(如
std::mutex
) -
当构造对象开销较大时(如深拷贝)
-
希望避免创建临时对象时
3. 迭代器失效问题
-
所有指向插入位置之前的迭代器和引用仍然有效
-
但插入位置之后的迭代器可能失效(因为
vector
可能需要扩容)
示例代码
#include <vector>
#include <string>
#include <iostream>struct Person {std::string name;int age;Person(std::string n, int a) : name(std::move(n)), age(a) {std::cout << "调用 Person 构造函数\n";}// 拷贝构造函数Person(const Person&) { std::cout << "调用 Person 拷贝构造函数\n"; }
};int main() {std::vector<Person> people;// 使用 emplace 直接在 vector 中构造 Personpeople.emplace(people.end(), "Alice", 30); // 不会调用拷贝构造函数!// 如果用 insert,必须先构造临时对象,再拷贝:// people.insert(people.end(), Person("Bob", 25)); // 会调用拷贝构造函数for (const auto& p : people) {std::cout << p.name << ", " << p.age << "\n";}
}
输出:
调用 Person 构造函数
Alice, 30
可以看到,emplace
直接调用了构造函数,而没有调用拷贝构造函数,效率更高。
与 insert
的对比
方法 | 是否需要临时对象? | 适用场景 |
---|---|---|
insert | 必须构造临时对象 | 适用于已有对象的情况 |
emplace | 直接构造,无需临时对象 | 适用于直接传递构造参数的情况 |
示例对比
// 使用 insert(需要构造临时对象)
vec.insert(vec.begin(), MyClass(1, 2, 3)); // 先构造,再拷贝/移动// 使用 emplace(直接构造)
vec.emplace(vec.begin(), 1, 2, 3); // 直接在 vector 内存中构造
时间复杂度
-
O(1)(如果插入到
end()
,且vector
不需要扩容) -
O(n)(如果插入到中间,需要移动后续元素)
注意事项
-
推荐
如果只需要在emplace_back
在末尾插入vector
末尾插入,优先使用emplace_back
,更高效。 -
参数求值顺序问题
C++17 之前,emplace
的参数求值顺序未定义,可能导致意外行为。 -
完美转发(Perfect Forwarding)
emplace
使用变长模板 + 完美转发,确保参数以最优方式传递。
总结
✅ 适用场景:
-
需要高效构造不可拷贝/不可移动的对象
-
避免临时对象的创建
❌ 不适用场景:
-
已有现成对象时,直接
push_back
或insert
即可
我们可以知道了,这个emplace的用处比较多,我们可以通过它来插入数据,而且这个插入还比较多样,我们可以指定位置插入数据,而且这个位置可以连续插入多个数据,这个我们可以通过在insert与emplace的对比中知道,其中的emplace不用手动构造对象,可以直接把后面的参数进行转换成对应的类型,就不需要我们自己显式实例化了。
3.5.2vector::emplace_back
我们自己通过对emplace的了解,我们也知道了emplace_back相当于我们的push_back,即尾插,我们要自己实操一下这个函数:
#define _CRT_SECURE_NO_WARNINGS 1
#include<vector>
#include<iostream>
#include<string>
using namespace std;
//vector::emplace_back的使用
int main()
{vector<string> v;string s1("Hello world");string s2("Hello CSDN");string s3("Hello");//push_backv.push_back(s1);v.push_back(string("Happy"));v.push_back({ "duanwujie" });//emplace_backv.emplace_back(s2);v.emplace_back(string("Hello"));//错误写法v.emplace_back({ "world" });return 0;
}
如果是自定义类型如何使用呢?
#define _CRT_SECURE_NO_WARNINGS 1
#include<vector>
#include<iostream>
#include<string>
using namespace std;
//自定义类型
class A
{
public:A(int a1=0,int a2=1):_a1(a1),_a2(a2){}
private:int _a1;int _a2;
};
//vector::emplace_back的使用
int main()
{vector<A> c;A aa(2, 2);//push_backc.push_back(aa);c.push_back(A(2, 2));//OKc.push_back({ 2,2 });//emplace_backc.emplace_back(aa);c.emplace_back(A(2, 2));//worryc.emplace_back({ 2,2 });//OK//自动识别参数的类型c.emplace_back(2, 2);return 0;
}
那么最终就会有一个报错:
因为这个在push_back算是隐性类型转化,而emplace_back参数数模板,类型是不确定的!无法实例化为一个类型。最后一个写法最高效,因为不用调用构造函数、拷贝构造函数,这是一个参数包,有几个参数就传递几个参数即可,之前的string无法传递两个即以上的参数,所以没法演示。
建议使用插入接口时使用emplace版本,因为这样更全面,效率也更高。
不过这些内容是在list(链表)中用得还是比较多的,在现阶段还是用得不是很多的!
3.6分配器
这个函数就是返回第二个模板参数的,现在没学内存池,只要了解一下即可。
3.7非成员函数的重载
里面就两个重载,相对于string的:
少了很多,少了比较,因为不需要比较,少了<<和>>的重载,因为这个是没必要的,都是线性的,访问可以通过[]来进行访问,就不需要这个了;也不需要getline了,因为如果是一个字符就直接通过库里面的get函数即可,如果是字符串,直接把存储类型改为string即可,这很简单了。
4.vector与string的区别
在vector中我们综合发现,没有了find函数,我们如果想要用find函数。就需要用这个算法库里面的find函数了:
std::find
我们可以用这个函数进行查找一个从[frist,last)区间中招一个值为val的值(不能为子序列),我们可以通过这个来查找:
#define _CRT_SECURE_NO_WARNINGS 1
#include<vector>
#include<iostream>
//find函数所包含的头文件
#include <algorithm>
using namespace std;
//std::find的正确使用
int main()
{vector<int> v = { 1,2,3,4,4,5,6,7,9 };auto c= find(v.begin(), v.end(), 5);//错误写法cout << c << endl;//正确写法cout << *c << endl;
}
这个函数只能找一个值,而不能这样写:
//std::find的错误使用
int main()
{vector<int> v = { 1,2,3,4,5,6,7,5,6,7,4,5,6 };vector<int> c = { 7,4,5 };int a = find(v.begin(), v.end(), c);return 0;
}
当我们把所有错误写法注释掉后的运行结果有:
这也是vector中常用的函数,由于返回的是一个迭代器,所以我们需要把它解引用。
5.总结
该讲主要讲解了vector中重要函数的用法以及一些需要注意的点,里面的函数与string的用法差不多,所以就不会讲解那么详细了,下一讲将讲解C++库中的排序和vector的底层。通过底层我们才能更好的模拟实现vector。
好了,这讲就到这里,喜欢的可以一键三连哦!