当前位置: 首页 > news >正文

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)(如果插入到中间,需要移动后续元素)


注意事项
  1. 推荐 emplace_back 在末尾插入

    如果只需要在 vector 末尾插入,优先使用 emplace_back,更高效。
  2. 参数求值顺序问题

    C++17 之前,emplace 的参数求值顺序未定义,可能导致意外行为。
  3. 完美转发(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。

好了,这讲就到这里,喜欢的可以一键三连哦!

http://www.xdnf.cn/news/531955.html

相关文章:

  • python-leetcode 67.寻找两个正序数组中的中位数
  • 如何在 Windows 11 或 10 上安装 Fliqlo 时钟屏保
  • CSS attr() 函数详解
  • HJ3 明明的随机数【牛客网】
  • 11.4/Q1,GBD数据库最新文章解读
  • threejs制作上升的小球
  • Kruise Rollout多批次发布
  • 3D 数据交换格式(.3DXML)简介
  • PyTorch Geometric(PyG):基于PyTorch的图神经网络(GNN)开发框架
  • 如何评估开源商城小程序源码的基础防护能力?
  • SCAU18924--二叉树的宽度多解
  • uniapp打包H5,输入网址空白情况
  • 样本复杂性:机器学习的数据效率密码
  • 【Vite】静态资源的动态访问
  • Libero离线IP安装
  • JWT : JSON Web Token
  • Linux 常用命令
  • 华为云Flexus+DeepSeek征文|基于华为云Flexus云服务的云服务器单机部署Dify-LLM应用开发平台
  • 力扣HOT100之二叉树:230. 二叉搜索树中第 K 小的元素
  • 【高德开放平台-注册安全分析报告】
  • LeetCode-滑动窗口-找到字符串中所有字母异位词
  • Swift 二分查找实战:精准定位第一个“Bug版本”(LeetCode 278)
  • 【栈 / 链表板子题】
  • 解决 uv run 时 ModuleNotFoundError: No module named ‘anthropic‘ 的完整指南
  • 【OSS】如何使用OSS提供的图片压缩服务
  • IDEA+AI 深度融合:重构高效开发的未来模式
  • 缺乏团队建设活动,如何增强凝聚力?
  • 隨筆20250519 Async+ThreadPoolTaskExecutor⾃定义线程池进阶实战
  • 基于卫星遥感的耕地非农化监测的技术原理简述
  • 论坛系统(中-2)