数组高阶应用(C++版)
在C++中,普通的数组(C-style array)、std::initializer_list
、 std::array
和std::vector
是四种不同的容器类型,它们各自有不同的特性和用途。下面是对它们进行详细比较和解释。
1. 普通数组(C-style Array)
普通数组是C++中最基本的数组类型,它在C语言中就已经存在。
特点:
- 固定大小: 数组的大小在声明时确定,不能动态调整。
- 编译时分配内存: 数组的内存通常在栈上分配。
- 不具备任何额外的功能: 普通数组没有边界检查、长度查询等功能。
- 兼容性: 与C语言完全兼容,适用于需要与C代码交互的场景。
语法:
int arr[5] = {1, 2, 3, 4, 5};
使用限制:
- 普通数组不能作为函数的返回值(因为它会退化为指针)。
- 没有内置的成员函数,比如获取大小或进行边界检查。
2. initializer_list
std::initializer_list
是C++11引入的一种轻量级容器,专门用于初始化列表。
特点:
- 只读容器: 容器中的元素是只读的,无法修改。
- 常量大小: 容器的大小在初始化时确定,不能修改。
- 语法简洁: 使用花括号
{}
可以轻松初始化std::initializer_list
。
语法:
#include <initializer_list>
#include <iostream>void printList(std::initializer_list<int> list) {for (int elem : list) {std::cout << elem << " ";}std::cout << std::endl;
}int main() {printList({1, 2, 3, 4, 5});return 0;
}
主要用途:
- 通常用于函数的参数,以允许使用初始化列表作为函数的输入。
- 在构造函数中使用,以支持对象的初始化列表语法。
3. array
std::array
是C++11引入的标准库容器,封装了固定大小的数组,提供了更丰富的功能。
特点:
- 固定大小: 与普通数组一样,大小在编译时确定,不能动态调整。
- 强类型安全: 提供了类型安全的接口,避免了指针退化等问题。
- 支持STL算法: 可以与标准库算法和容器互操作。
- 丰富的成员函数: 包括
size()
、at()
(带边界检查)等。 - 内存布局与普通数组相同: 内存布局与普通数组相同,所以开销极低。
语法:
#include <array>
#include <iostream>int main() {std::array<int, 5> arr = {1, 2, 3, 4, 5};// 使用 at() 访问元素,带边界检查std::cout << "Element at index 2: " << arr.at(2) << std::endl;// 使用迭代器for (auto it = arr.begin(); it != arr.end(); ++it) {std::cout << *it << " ";}std::cout << std::endl;return 0;
}
优势:
- 提供了与
std::vector
类似的接口,但由于其大小是固定的,所以比std::vector
更轻量。 - 在需要使用固定大小数组的场景下,
std::array
是普通数组的更安全、更方便的替代品。
总结
- 普通数组: 基本的数组类型,大小固定,轻量但功能有限,容易出错(如数组越界)。
std::initializer_list
: 只读容器,专门用于初始化列表,常用于函数参数。std::array
: 封装了固定大小数组,提供了强类型安全和更丰富的功能,是普通数组的更好的替代品。
4. vector
相对于前面三者,std::vector
是一种动态大小的容器,提供了更为强大的功能。它是C++标准模板库(STL)中最常用的序列容器之一。之前有写过vector的模拟实现的博客:链接
特点
-
动态大小:
std::vector
的大小是可变的,可以根据需要动态增加或减少元素。这与普通数组和std::array
是最大的不同。 -
自动管理内存:
std::vector
会自动管理它的内存,包括分配、扩展和释放。用户不需要手动管理内存,这避免了内存泄漏等问题。 -
提供丰富的成员函数:
std::vector
提供了一系列成员函数,比如push_back()
、emplace_back()
、insert()
、erase()
等,使得操作更加便捷。 -
随机访问:
std::vector
支持常数时间的随机访问(operator[]
和at()
),就像普通数组一样。 -
内存连续性:
std::vector
的元素是连续存储的,这意味着它可以与传统的C-style数组进行高效的互操作,并且可以利用底层硬件的缓存机制提高性能。 -
支持STL算法:
std::vector
可以与STL中的各种算法(如sort()
、find()
等)无缝配合使用。
vector 的用法
基本用法:
#include <vector>
#include <iostream>int main() {std::vector<int> vec = {1, 2, 3, 4, 5};// 添加元素vec.push_back(6);// 访问元素std::cout << "Element at index 2: " << vec[2] << std::endl;// 使用迭代器遍历for (auto it = vec.begin(); it != vec.end(); ++it) {std::cout << *it << " ";}std::cout << std::endl;// 删除元素vec.pop_back();return 0;
}
与其它容器的区别
-
与普通数组(C-style array)相比:
- 普通数组的大小是固定的,不能在运行时改变;而
std::vector
可以动态调整大小。 - 普通数组不提供边界检查,容易造成越界访问;而
std::vector
提供了at()
方法,具有边界检查。 - 普通数组不提供丰富的成员函数,操作不如
std::vector
方便。
- 普通数组的大小是固定的,不能在运行时改变;而
-
与
std::initializer_list
相比:std::initializer_list
是一个轻量级的只读容器,不能修改和扩展;而std::vector
是一个可读写的容器,支持动态大小。std::initializer_list
常用于函数参数的初始化;而std::vector
适用于更多场景。
-
与
std::array
相比:std::array
的大小是固定的,类似于普通数组,不能在运行时改变;而std::vector
支持动态大小。std::array
的性能通常略优于std::vector
,因为它没有动态分配和管理内存的开销;而std::vector
由于需要动态分配内存,有时会比std::array
稍慢。
总结
- 普通数组: 固定大小,轻量但功能有限,容易出错(如数组越界)。
std::initializer_list
: 只读容器,专门用于初始化列表,常用于函数参数。std::array
: 封装了固定大小数组,提供了更安全、更方便的替代品。std::vector
: 动态大小容器,自动管理内存,功能丰富,是大多数情况下的首选容器。
std::vector
的灵活性和功能使它成为C++编程中最常用的容器之一,适用于需要动态大小和频繁增删元素的场景。
每种容器都有其特定的应用场景,选择时要根据具体需求来决定。
扩展:迭代器失效问题
在C++中,std::vector
是一个动态数组,随着元素的增加或删除,其底层内存可能会重新分配。当发生重新分配时,所有指向旧存储空间的迭代器、指针和引用将失效,导致可能的程序错误。这就是所谓的迭代器失效问题。
迭代器失效的情况
std::vector
中迭代器可能失效的主要情况有以下几种:
-
插入元素时:
-
当
std::vector
需要扩展容量时,可能会重新分配更大的内存,并将现有元素复制到新的内存位置。此时,所有旧的迭代器、指针、和引用都会失效。 -
如果插入的位置不是
vector
的末尾,所有从插入点开始的迭代器也会失效,因为后面的元素要向后移动。
-
-
删除元素时:
- 如果删除元素,所有指向被删除元素以及它后面元素的迭代器都会失效,因为元素会被向前移动来填补删除的位置。
-
clear()
和erase()
操作:clear()
会删除所有元素,使所有迭代器失效。erase()
会使指向被删除元素和它后面的元素的迭代器失效。
错误示范
std::vector<int> v = {1, 2, 3, 4, 5};for (auto it = v.begin(); it != v.end(); ++it) {if (*it % 2 == 0) {it = v.erase(it); }
}
erase已经返回下一个元素的位置,如果再加加可能起不到遍历的效果,甚至会访问到不属于vector的空间。
如何处理迭代器失效
要处理 std::vector
的迭代器失效问题,可以采用以下几种方法:
1. 避免在迭代时改变容器
这是最简单的策略。如果在遍历 std::vector
的时候不修改它,迭代器就不会失效。
std::vector<int> v = {1, 2, 3, 4, 5};for (auto it = v.begin(); it != v.end(); ++it) {std::cout << *it << std::endl;
}
2. 重新获取迭代器
当执行插入或删除操作时,可以重新获取迭代器。对于插入操作,插入元素后可以通过返回的新迭代器继续操作。对于删除操作,erase
返回的迭代器指向删除元素之后的下一个元素。
std::vector<int> v = {1, 2, 3, 4, 5};for (auto it = v.begin(); it != v.end(); /* nothing */) {if (*it % 2 == 0) {it = v.erase(it); // erase 返回删除元素之后的迭代器} else {++it;}
}
3. 预先分配足够的空间
在插入大量元素之前,可以使用 reserve()
方法提前分配足够的空间,避免频繁的内存重新分配,减少迭代器失效的机会。
std::vector<int> v;
v.reserve(1000); // 预分配空间,避免多次重新分配for (int i = 0; i < 1000; ++i) {v.push_back(i); // 不会导致迭代器失效
}
4. 使用索引而不是迭代器
在遍历 std::vector
时,使用索引而不是迭代器,可以避免迭代器失效的问题。
std::vector<int> v = {1, 2, 3, 4, 5};for (std::size_t i = 0; i < v.size(); /* nothing */) {if (v[i] % 2 == 0) {v.erase(v.begin() + i); // 删除偶数} else {++i;}
}
5. 使用双向迭代器结构的算法
有些标准库算法,如 remove_if
,不会修改容器的大小,而是重排容器元素。这些算法不会导致迭代器失效。你可以结合 erase
和 remove_if
来安全地删除符合条件的元素。
std::vector<int> v = {1, 2, 3, 4, 5};
v.erase(std::remove_if(v.begin(), v.end(), [](int x) { return x % 2 == 0; }), v.end()
);
在这里,std::remove_if
会把不符合条件的元素移动到容器的末尾,而 erase
则将那些元素实际删除。这样可以避免多次 erase
操作造成的迭代器失效。
处理方式汇总
- 插入元素时,特别是插入到
vector
末尾时,尽量提前调用reserve()
预分配空间。 - 删除元素时,使用
erase
返回的迭代器或结合remove_if
等标准库算法。 - 遍历容器时,可以使用索引或重新获取迭代器,避免指向失效的迭代器。
- 在多线程场景下,修改
vector
需要特别小心同步问题,可以考虑使用锁或其他并发安全容器。
如果你能看到这里,给你点个赞,如果对你有帮助的话不妨点赞支持一下~
相关文章:

数组高阶应用(C++版)
在C中,普通的数组(C-style array)、std::initializer_list 、 std::array和std::vector 是四种不同的容器类型,它们各自有不同的特性和用途。下面是对它们进行详细比较和解释。 1. 普通数组(C-style Array)…...

Spring(四)多线程+异步任务执行服务+常见的Enable注解+SpringUnit测试
Spring多线程 Spring通过任务执行器(TaskExecutor)来实现多线程和并发编程ThreadPoolTaskExecutor实现一个基于线程池的TaskExecutor配置类中EnableAsync开启对异步任务的支持使用Async声明该任务为异步 ①、配置类 Configuration ComponentScan(&quo…...

解析与实现二叉树
在数据结构与算法的学习中,二叉树无疑是一个重要且实用的数据结构。它不仅在理论上具有深刻的研究价值,更在实际应用中广泛存在,如搜索引擎的索引结构、文件系统的目录树、数据库的索引、游戏开发中的场景管理等等。本文将深入探讨二叉树的基…...

Java面向对象——内部类(成员内部类、静态内部类、局部内部类、匿名内部类,完整详解附有代码+案例)
文章目录 内部类17.1概述17.2成员内部类17.2.1 获取成员内部类对象17.2.2 成员内部类内存图 17.3静态内部类17.4局部内部类17.5匿名内部类17.5.1概述 内部类 17.1概述 写在一个类里面的类叫内部类,即 在一个类的里面再定义一个类。 如,A类的里面的定义B类&#x…...

操作系统笔记三
进程 把一个静态程序通过OS在内存中让cpu执行起来的动态执行过程叫进程 写代码都是用户态,而进程在执行过程中需要完成特定的功能,这些功能呢只有操作系统能提供,比如说读写文件,读写文件的过程是与硬盘打交道,这个过程…...

uniapp快速入门教程,内容来源于官方文档,仅仅记录快速入门需要了解到的知识点
uniapp快速入门教程,内容来源于官方文档,仅仅记录快速入门需要了解到的知识点 目录 介绍uniapp 介绍uniapp x 介绍功能框架图创建项目&发布组件/标签的变化js的变化css的变化工程结构和页面管理 pages.jsonmanifest.json 应用配置组件easycom组件规…...

基于微信小程序的商品展示+ssm(lw+演示+源码+运行)
商品展示系统 摘 要 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,微信小程序被用户普遍使用,为方…...

【Linux】常用指令(下)(内含more、less、 head、tail、date、find、grep、zip、tar以及学习笔记)
文章目录 前言1. more指令2. less指令(重要)3. head指令4. tail指令5. 管道(做到学会使用即可)6. date指令6.1 时间戳 7. cal指令8. find指令9. grep指令10. zip/unzip指令11. tar指令 前言 Linux下的常用指令终于要在本文落下帷…...

DesignMode__unity__抽象工厂模式在unity中的应用、用单例模式进行资源加载
目录 抽象工厂模式 思维导图 接口(抽象类) 工厂接口 抽象产品类 抽象武器接口 抽象人物接口 具体工厂和具体产品 具体工厂 (1)产品接口,生成具体人物 (2)武器接口,生成具体…...

Leetcode3289. 数字小镇中的捣蛋鬼
Every day a Leetcode 题目来源:3289. 数字小镇中的捣蛋鬼 解法1:哈希 代码: /** lc appleetcode.cn id3289 langcpp** [3289] 数字小镇中的捣蛋鬼*/// lc codestart class Solution { public:vector<int> getSneakyNumbers(vector…...

13_Python的高阶函数
高阶函数 高阶函数是Python编程中一个非常强大和有用的特性,它们允许程序员编写更简洁、更抽象的代码。 Python中的高阶函数是那些至少满足以下一个条件的函数: 接受一个或多个函数作为输入(也就是说,它的参数之一是函数&#…...

清空当前机器所有Docker容器和镜像
sudo docker stop $(sudo docker ps -aq) sudo docker rm $(sudo docker ps -aq) sudo docker rmi $(sudo docker images -q)删除当前机器上的所有Docker镜像是一个高风险操作,因为它会删除所有镜像,包括那些可能正在被容器使用的镜像。在执行此操作之前…...

FreeRTOS学习——Systick中断、SVC中断、PendSV中断
FreeRTOS学习——接口宏portmacro.h,仅用于记录自己阅读与学习源码 FreeRTOS Kernel V10.5.1 port :GCC/ARM_CM7 文章目录 Systick源码触发方式 SVC源码触发方式 PendSV源码触发方式 相关汇编指令 Systick 源码 在Systick中断xPortSysTickHandler中&am…...

汇量科技大数据面试题及参考答案
如何在 SQL 中处理三个字段完全一样的去重?在 Scala 中又该如何实现? 在 SQL 中,可以使用多种方法来处理三个字段完全一样的去重。一种常见的方法是使用 DISTINCT 关键字结合多个字段来实现。例如,假设有表 table_name,包含字段 field1、field2 和 field3,可以使用以下 S…...

移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——14.AVL树
1.AVL 树 1.1AVL 树的概念 二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查 找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii 和E.M.Landis在1962…...

Python 的数据类型与操作
一、常用内置类型(Built - in Types) Python 拥有多种内置数据类型,这些类型满足了各种编程需求,从简单的数据存储到复杂的数据结构表示。 1. 数值类型(Numeric Types) 整数(int)&a…...

Python燃烧废气排放推断算法模型
🎯要点 宏观能耗场景模型参数化输入数据,分析可视化输出结果,使用场景时间序列数据模型及定量和定性指标使用线图和箱线图、饼图、散点图、堆积条形图、桑基图等可视化模型输出结果根据气体排放过程得出其时间序列关系,使用推断模…...

Qt中多语言的操作(以QtCreator为例)
1、首先,我们在代码中与文本相关的且需要支持多语言的地方,用tr来包含多语言key(多语言key是我们自己定义的),如下 //举例 QPushButton* btnnew QPushButton(this); btn->move(20,20); btn->resize(100,50); //…...

计算机毕业设计 社区医疗服务系统的设计与实现 Java实战项目 附源码+文档+视频讲解
博主介绍:✌从事软件开发10年之余,专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ 🍅文末获取源码联系🍅 👇🏻 精…...

html+css学习
html 元素 html元素是HTML的根元素,一个文档只能有一个,其他所有元素都是其后代元素 html有一个属性为lang,其作用是: 帮助语言合成工具确定要使用的发音帮助翻译工具确定要使用的翻译规则 当属性lang“en”则表示告诉其浏览器…...

2.gitlab ce 细粒度的权限控制
需求: 在提交merge reqeust时,必须指定审核人,并且要选审核人清单里的 有个code owners应该可以做到(gitlab ce应该也可以用) 下面是参考的文档 细粒度的代码权限怎么做?极狐GitLab 代码所有者来帮忙 -…...

G - Merchant Takahashi / F - Useless for LIS
G - Merchant Takahashi 首先考虑暴力 DP。 设最后一步走到编号 ii 的城镇的方案的最大收益为 fifi,则每次集市相当于是 fTi←fj−C∣Ti−j∣Pi(1≤j≤n)。 这样每次可以通过枚举 j 来转移,这样总时间复杂度是 O(nm) 的&…...

自然语言处理实例
引子:基于聊天机器人项目的自然语言处理(NLP)学习路线 自然语言处理(Natural Language Processing,简称 NLP)是人工智能的重要分支,旨在帮助计算机理解、生成和处理人类语言。NLP 技术广泛应用于搜索引擎、机器翻译、语音识别、文本摘要、情感分析、对话系统等领域。为…...

『功能项目』主角属性值显示【75】
本章项目成果展示 我们打开上一篇74穿戴装备的项目, 本章要做的事情是制作主角属性界面,实现在面板上显示主角的攻击力等数值 制作一个简易的主角界面(创建Image与Text显示即可) 创建一个空物体 重命名为PlayerInfo 在其子级下创…...

单片机嵌入式编程中常用技术点
Open CV,QT,Linux,多线程,网络编程,文件编程在单片机嵌入式编程中,这些技术在单片机嵌入式编程中的作用: 一、OpenCV 在单片机嵌入式编程中,虽然单片机的计算能力相对有限…...

【毕业论文+源码】基于ASP+NET的人事管理系统
引言 人事管理系统是针对企业内部人事管理设计,分角色实现对公司部门及各部门员工的增、删、改、查以及对员工考勤的管理。 编写目的: 在系统需求分析的基础上,对需求分析中产生的功能模块进行过程描述,设计功能模块的内部细节&…...

计算机毕业设计 校园志愿者管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解
博主介绍:✌从事软件开发10年之余,专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ 🍅文末获取源码联系🍅 👇🏻 精…...

速通LLaMA2:《Llama 2: Open Foundation and Fine-Tuned Chat Models》全文解读
文章目录 概览LLaMA和LLaMA2的区别AbstractIntroductionPretrainingFine-tuning1. 概括2、Supervised Fine-Tuning(SFT)3、⭐Reinforcement Learning with Human Feedback(RLHF)🔺总览Training Objectives:…...

如何使用VM中win10搭建Hfish蜜罐(危险感知平台)。从下载到部署详细教程
得而不惜就该死。 -----古月方源 引言:最近跟一个老师做东西,叫我搞清楚蜜罐的搭建和一些底层逻辑,所以记录一下。 一、实验准备 (一)win10虚拟机 (若有需要可以后台私信) (二&…...

Rust: AES 加密算法库
在Rust中,进行AES加密通常会用到一些现有的库,因为Rust标准库中并不直接提供AES加密的API。一个非常流行的库是crypto-box或者更广泛使用的ring库,但ring库由于依赖问题有时可能难以编译,另一个常用的库是cryptography的Rust绑定&…...