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

C++ Vector:动态数组的高效使用指南

🔥个人主页:胡萝卜3.0

📖个人专栏:  《C语言》、《数据结构》 、《C++干货分享》、LeetCode&牛客代码强化刷题

⭐️人生格言:不试试怎么知道自己行不行


🎥胡萝卜3.0🌸的简介:



目录

一、初识Vector:C++的动态数组容器

二. vector核心接口:必学的几个高频操作

2.1 定义和初始化(构造函数)

2.2 迭代器:遍历 vector 的 "万能工具"

2.3 空间管理:size、capacity 与扩容策略(reserve)

2.4 增删查改:日常开发高频操作

三. vector 实战:OJ 算法题中的高频用

3.1 只出现一次的数字

3.2 杨辉三角

结尾


一、初识Vector:C++的动态数组容器

Vector 本质上是一个能够动态增长的数组,它在保留普通数组随机访问高效这一核心优势的同时,解决了其固定容量、不够灵活的痛点。通过自动管理内存和自动扩容的机制,它让你可以像使用无限延伸的数组一样,无需关心底层内存分配,就能高效、便捷地存储和访问一系列元素。

vector的核心特性

  • 动态数组:底层在连续的内存空间中存储元素。

  • 随机访问:通过 [] 运算符或 at() 方法,可以在 O(1) 时间复杂度内访问任何元素。

  • 动态扩容:当插入新元素导致当前容量不足时,vector 会自动申请一块更大的内存(通常是原大小的1.5或2倍),将原有元素拷贝移动到新内存,并释放旧内存。

  • 尾部操作高效:在末尾插入(push_back)或删除(pop_back)元素,时间复杂度为 O(1)(不考虑扩容开销)。

  • 中间/头部操作低效:在中间或头部插入或删除元素,需要移动后续的所有元素以保持连续性,时间复杂度为 O(n)

二. vector核心接口:必学的几个高频操作

  • 我们有了前面学习 string 的基础,再来学习 vector 就轻松很多了,而且也不需要记住所有接口,重点掌握 “构造、迭代器、空间管理、增删查改” 四大类核心接口,即可覆盖 90% 以上场景。

vector是一个很奇怪的容器,它的里面没有实现流插入和流提取,所以我们就需要自己书写一个打印的函数(很简单的),话不多说,直接上代码:

  • vector.cpp
void print(const vector<int>& v)
{//下标+[]for (size_t i = 0; i < v.size(); i++){cout << v[i] << " ";}//迭代器/*vector<int>::const_iterator it = v.begin();while (it != v.end()){cout << *it << " ";++it;}*/////范围for//支持迭代器的都支持范围for/*for (auto e : v){cout << e << " ";}*/
}
2.1 定义和初始化(构造函数)
(constructor)构造函数声明接口说明
vector()(重点)无参构造
vector(size_type n, const value_type& val = value_type())构造并初始化n个val
vector (const vector& x); (重点)拷贝构造
vector (InputIterator first, InputIterator last);使用迭代器进行初始化构造

以及C++11中一个比较好用的一个构造:

ok,接下来我们一一来看:

  • vector()  无参构造
//无参构造
vector<int> v;
  • vector(size_type n, const value_type& val = value_type())构造并初始化n个val
//创建10个数据,10个数据都是1
vector<int> v1(10, 1);
  • vector (const vector& x);  拷贝构造
//拷贝构造
vector<int> v1(10, 1);
vector<int> v2(v1);
  • vector (InputIterator first, InputIterator last);   使用迭代器进行初始化构造

这里使用迭代器进行初始化,可以传vector类型,也可以传其他对象的迭代器(前提:类型之间可以进行转换)

1、传vector类型

//使用迭代器空间初始化
vector<int> v1(10, 1);
vector<int> v3(v1.begin(), v1.end());

2、传其他类型的对象(stirng类)

//使用迭代器空间初始化
string s("hello world");
vector<int> v3(s.begin(), s.end());

接下来,我们一起来看看C++11中给我们提供的一个特殊构造:
1、用法

vector<int> v4 = { 1,2,3,4,5,6,7,8,9,10 };
//更严格的写法
vector<int> v4({ 1,2,3,4,5,6,7,8,9,10 });

这时候就有uu想问了,为什么可以这样写?它的底层逻辑是啥?
2、底层逻辑

在C++11中,用了 { } ,编译器就会自动认为是initializer_list,我们可以使用 { } 括任意数量的值去初始化vector对象

底层原理:
内部有两个指针,一个指针指向开头,一个指针指向结尾,可以认为是内部开了一块空间把数组存下来了。

嗯?啥意思?有点不太理解

ok,我们来看下面这张图上的内容:

我们把数组传给initializer_list中的参数il,这个参数其实是创建了一个对象,然后这个对象用指针指向这个数组的开始和结尾,内部底层就相当于写了一个范围for,然后这个范围for就遍历这个initializer_list中的参数il构造对象,然后把它给push_back到vector中

2.2 迭代器:遍历 vector 的 "万能工具"
  • 迭代器是访问容器元素的通用接口,vector 的迭代器本质是 “封装的指针”,支持遍历、取值、移动等操作。
iterator的使用接口说明
begin + end(重点)获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator
rbegin + rend获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的reverse_iterator
  • begin + end(重点)

begin()是指向数据开头的迭代器,end()是指向最后一个有效数据的下一个位置

  • 代码演示:
void testVector3()
{vector<int> v({ 1,2,3,4,5,6,7,8,9,10 });auto it = v.begin();//指向开头数据的迭代器,也就是指向v[0]while (it != v.end())//end()是指向最后一个数据的下一个位置的迭代器{cout << *it << " ";++it;}}
  • rbegin + rend

rbegin 和 rend 正好与begin 和end相反。rbegin是指向最后一个数据的迭代器,end是指向开头数据的前一个位置的迭代器。

  • 代码演示:
void testVector3()
{vector<int> v({ 1,2,3,4,5,6,7,8,9,10 });auto it2 = v.rbegin();//指向最后一个数据的迭代器,也就是指向v[9]while (it2 != v.rend())//rend()是指向开头数据的上一个位置的迭代器{cout << *it2 << " ";++it2;}
}
2.3 空间管理:size、capacity 与扩容策略(reserve)
  • vector 的 “空间” 分为 size(有效元素个数)和 capacity(最大可容纳元素个数,不含结束标志),理解两者区别是避免扩容开销的关键。

核心接口:

容量空间接口说明
size获取数据个数
capacity获取容量大小
empty判断是否为空
resize(重点)改变vector的size
reserve (重点)改变vector的capacity
  • size

获取有效数据个数

void testVector3()
{vector<int> v({ 1,2,3,4,5,6,7,8,9,10 });size_t size=v.size();//求出有效数据个数cout << size << endl;
}
  • capacity

获取空间大小

void testVector3()
{vector<int> v({ 1,2,3,4,5,6,7,8,9,10 });size_t capacity = v.capacity();cout << capacity << endl;
}
  • empty

判断是否为空

void testVector4()
{vector<int> v({ 1,2,3,4,5,6,7,8,9,10 });bool ret = v.empty();cout << ret << endl;if (!ret){cout << "非空" << endl;}else{cout << "空的" << endl;}
}
  • resize(重点)

调整 size 到 n(缺省用 0 填充)

1、开空间并插入值

void testVector4()
{vector<int> v;//开10个空间,没有传要插入的值,默认插入0v.resize(10);for (auto e : v){cout << e << " ";}vector<int> v1;//开10个空间,插入1v1.resize(10, 1);for (auto e : v1){cout << e << " ";}
}

2、reszie也可以进行缩容操作,如果n<size(),就保留前n个数据

void testVector()
{	vector<int> v2({ 1,2,3,4,5,6,7,8,9,10 });//5<v2.size() 保留前5个v2.resize(5);for (auto e : v2){cout << e << " ";}
}

resize真正的用途是——开空间并插入值,如果对象中有数据,则在后面继续添加(情况很少)!!!

  • reserve (重点)

改变capacity

void testVector5()
{vector<int> v({ 1,2,3,4,6 });for (auto e : v){cout << e << " ";}cout << endl;cout << v.capacity() << endl;cout << endl;//现在我想再插入5个数据,但是空间不够了,需要扩容//将空间扩到10v.reserve(10);v.push_back(5);v.push_back(7);v.push_back(8);v.push_back(9);v.push_back(10);for (auto e : v){cout << e << " ";}cout << endl;cout << v.capacity() << endl;
}

关键:vector 扩容策略

vector 扩容时会分配新内存、迁移旧元素、释放旧内存,这个过程耗时较高。不同编译器扩容倍数不同:

  • VS :1.5 倍扩容(如 capacity 从 4→6→9→13...);
  • G++ :2 倍扩容(如 capacity 从 4→8→16→32...)。

reserve的真正使用场景:提前开空间,减少扩容次数,提高效率

注意:在vector中,reserve是要多少空间就开多少空间,不会多开空间

2.4 增删查改:日常开发高频操作
  • vector 的增删查改接口设计简洁,重点关注 “尾插 / 尾删” 的高效性和 “中间插入 / 删除” 的注意事项。
vector增删查改接口说明
push_back(重点)尾插
pop_back (重点)尾删
insert在position之前插入val
erase删除position位置的数据
operator[] (重点)像数组一样访问

当然还有 emplace 和 emplace_back等接口,后面会一一介绍~~~

  • push_back(重点)

在尾部插入数据

void testVector6()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);for (auto e : v){cout << e << " ";}
}
  • pop_back (重点)

删除尾部数据

void testVector6()
{vector<int> v;//尾插v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);for (auto e : v){cout << e << " ";}cout << endl;//尾删v.pop_back();v.pop_back();for (auto e : v){cout << e << " ";}
}

执行结果:

vector中没有提供头插和头删的接口,这是因为头插和头删的时间复杂度都是O(n),不建议使用头插和头删,但是可以使用insert和erase

  • insert

在任意位置插入数据,insert用迭代器传位置

void testVector6()
{vector<int> v;//尾插v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);for (auto e : v){cout << e << " ";}cout << endl;//头插v.insert(v.begin(), 0);//在中间插入v.insert(v.begin()+2, 10);for (auto e : v){cout << e << " ";}
}

执行结果:

  • erase

在任意位置删除数据,erase用迭代器传位置

void testVector6()
{vector<int> v;//尾插v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);for (auto e : v){cout << e << " ";}cout << endl;//头删v.erase(v.begin());//删除中间位置数据v.erase(v.begin()+2);for (auto e : v){cout << e << " ";}
}

执行结果:

  • operator[] (重点)

vector中有operator[] 这个接口,我们就可以像数组一样使用下标+[ ] 的方式修改和遍历vector对象

void testVector7()
{vector<int> v({ 1,2,3,5,6,7,8,9 });//遍历for (size_t i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;//修改下标为0的数据v[0] = 0;//下标+[]for (size_t i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;//遍历+修改for (size_t i = 0; i < v.size(); i++){v[i]++;cout << v[i] << " ";}
}
  • emplace

emplace==insert

void testVector8()
{vector<int> v;//尾插v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);for (auto e : v){cout << e << " ";}cout << endl;//头插v.emplace(v.begin(), 0);//在中间插入v.emplace(v.begin() + 2, 10);for (auto e : v){cout << e << " ";}
}
  • emplace_back

emplace_back==push_back

但是他们两个还是有些区别的,我们一起来看一下~

ok,我们先来看一段代码:

为什么上面这段代码无法运行?这是因为AA中不支持流插入

那我们该怎么解决这个问题呢?

解决方法:

  1. 让AA支持流插入
  2. 用->

这时候就有UU想问了,为什么可以这样用?
当迭代器指向的数据是这种符合类型的类,数据是公有的,可以使用 -> ,可以认为底层的指针是数组AA的指针

ok,这时候又有UU想说:嗯?不是要说emplace_back吗?怎么说这个,这是因为emplace_back只有在这种情景下,才能说清和push_back的区别。

差异:

ok ,在现阶段中,我们就认为emplace_back和push_back 是一样的就好了~~~

三. vector 实战:OJ 算法题中的高频用法

  • vector  是算法刷题的 “主力容器”,以下 2 道经典 OJ 题,覆盖 vector 的核心使用场景。
3.1 只出现一次的数字
  • 题目链接:

https://leetcode.cn/problems/single-number/

  • 题目描述

  • 算法代码
class Solution {
public:int singleNumber(vector<int>& nums) {int tmp=0;for(auto e:nums){tmp^=e;}return tmp;}
};
3.2 杨辉三角
  • 题目链接

https://leetcode.cn/problems/pascals-triangle/

  • 题目描述

  • 算法代码
class Solution {
public:vector<vector<int>> generate(int numRows) {//创建一个vector<vector<int>>对象vector<vector<int>> vv;//行,匿名对象初始化vv.resize(numRows,vector<int>());//列for(size_t i=0;i<numRows;i++){vv[i].resize(i+1,1);}for(size_t i=2;i<vv.size();i++){for(size_t j=1;j<vv[i].size()-1;++j){vv[i][j]=vv[i-1][j-1]+vv[i-1][j];}}return vv;}
};
  • 题目解析

要真正的掌握这道算法题,我们就要对vector的底层有一点的了解(后面会介绍的vector的底层),我们vector其实和我们在数据结构中学的顺序表的是差不多的,既然是这样的话,那我们是不是就可以写出vector的私有成员:

class vector<int>
{
private:int* _a;size_t _size;size_t _capacity;
};

那我们是怎么构造出一个二维数组的呢?

  • 先用int 实例化出vector<int>的类

  • 再用vector<int>作为模板参数去实例化出vector<vector<int>>的类

用vector<vector<int>>实例化出的对象中的数组中的成员类型都是vector<int>,vector<int>实例化出的对象中的数组是一个成员类型都是int的数组。

通过上面的了解,我们就知道这个二维数组是怎么实现的了~~~

ok,那接下来我们来看看是怎么访问数据的:

上图所演示的又是怎么完成的呢?

我们先来看看这个两个 [ ] 运算符重载是啥样的:
1、

 2、

对于二维数组的理解还是比较重要的!!!

结尾

ok,写到这里vector的使用基本上就已经结束了,看到这里的小伙伴给自己一个大大的👍~!!!

http://www.dtcms.com/a/503463.html

相关文章:

  • html5微网站漂亮网站
  • C++ 分配内存 new/malloc 区别
  • Respective英文单词学习
  • 网络排错全流程:从DNS解析到防火墙,逐层拆解常见问题
  • 移动端开发工具集锦
  • 使用Nvidia Video Codec(三) NvDecoder
  • 周口规划建设局网站wordpress模板中添加短代码
  • Linux小课堂: 命令手册系统深度解析之掌握 man 与 apropos 的核心技术机制
  • 阿里云做网站官网网站改版的seo注意事项
  • 每日算法刷题Day76:10.19:leetcode 二叉树12道题,用时3h
  • 【OS笔记11】:进程和线程9-死锁及其概念
  • 贪心算法1
  • 服务器搭建vllm框架并部署模型+cursor使用经验
  • Arduino采集温湿度、光照数据
  • 32HAL——外部中断
  • 网站建设会议议程新闻营销发稿平台
  • 【图像处理】CMKY色彩空间
  • 南宁建行 网站南通网站的优化
  • 构建AI智能体:六十八、集成学习:从三个臭皮匠到AI集体智慧的深度解析
  • 从入门到精通【Redis】Redis 典型应⽤ --- 分布式锁
  • 6.5 万维网(答案见原书P294)
  • CycloneDDS:跨主机多进程通信全解析
  • Java基础语法—类型转换、表达式的自动类型提升
  • CentOS8无法使用sudo提权
  • 软件工程师招聘信息网站数据库对网站开发的作用
  • Python核心数据结构与函数编程
  • Spring Boot 3零基础教程,WEB 开发 内容协商 接口返回 YAML 格式的数据 笔记35
  • 网站编程学北京上海网站建设公司
  • 查询土耳其公司商业登记册(工商报告),可以获取什么信息?
  • ip反查域名