现实世界中的C++ std::vector vs array

C++ std::vector vs array in the real world

提问人:GRardB 提问时间:6/24/2011 最后编辑:CommunityGRardB 更新时间:11/11/2023 访问量:26474

问:

我是C++的新手。我正在阅读Michael Dawson的“通过游戏编程开始C++”。但是,我对编程并不陌生。我刚刚完成了一章关于向量的章节,所以我有一个关于它们在现实世界中的使用的问题(我是一名计算机科学专业的学生,所以我还没有太多的实际经验)。

作者在每章末尾都有一个问答,其中一个是:

问:什么时候应该使用向量而不是数组?

答:几乎总是如此。向量既高效又灵活。它们确实需要比阵列更多的内存,但这种权衡几乎总是值得的。

你们怎么看?我记得我在一本Java书中学习过向量,但是在我的Comp. Sci.class入门课上,也没有在大学的数据结构课上介绍它们。我也从未见过它们用于任何编程任务(Java 和 C)。这让我觉得它们被使用得不多,尽管我知道学校代码和现实世界的代码可能截然不同。

我不需要被告知两种数据结构之间的差异;我非常了解他们。我想知道的是,作者是否在他的问答中给出了很好的建议,或者他是否只是想让初学者免于因管理固定大小的数据结构的复杂性而毁掉自己。另外,不管你对作者的建议有什么看法,你在现实世界中更经常看到的是什么?

C++ 数组 矢量 c++-standard-library

评论

2赞 Dair 6/24/2011
好吧,您可能从未在 C 中使用过向量,因为据我所知,C 没有通用编程、STL 或自己的向量,这意味着您只能动态分配数组......
5赞 Xepo 6/24/2011
我在 HP 工作,负责 250 万行代码库。我们努力在需要可调整大小的数组时随时使用向量。我从未见过学术界使用的 STL,我也不知道为什么,但相信我,它们肯定被用于现实世界的编程中。
4赞 Martin York 6/24/2011
我认为你读一本书是有原因的。听取作者的建议。读取 std::vector-is-so-much-sslow-than-plain-arrays
1赞 Glenn Teitelbaum 7/3/2015
在 C++11 中添加 为使数组对固定大小的用例更有用增加了很多std::array
0赞 Max Lybbert 12/23/2015
当你提到“Java 中的向量”时,我假设你说的是 .Java 也有一个名为 ( docs.oracle.com/javase/7/docs/api/java/util/Vector.html ) 的类型,它是一个可增长的数组,如 和 ,但也同步对容器的所有访问。通常,这会绊倒使用 Java 的 C++ 程序员,但这是需要注意的。ArrayList<E>Vector<E>ArrayListstd::vector

答:

7赞 Nicol Bolas 6/24/2011 #1

std::vector 只是一个可调整大小的数组。仅此而已。这不是您在 Data Structures 类中学到的东西,因为它不是一个智能数据结构。

在现实世界中,我看到了很多数组。但我也看到很多遗留代码库使用“C with Classes”风格的 C++ 编程。这并不意味着你应该以这种方式编程。

21赞 Dan 6/24/2011 #2

使用 a 而不是数组的最佳理由之一是 RAII 习语。基本上,为了使 c++ 代码具有异常安全性,任何动态分配的内存或其他资源都应该封装在对象中。这些对象应具有释放这些资源的析构函数。vector

当异常未得到处理时,唯一需要调用的东西是堆栈上对象的析构函数。如果在对象外部动态分配内存,并且在删除之前在某处抛出未捕获的异常,则存在内存泄漏。

这也是避免不得不记住使用的好方法。delete

您还应该查看 ,它为和其他 STL 容器提供了许多常用算法。std::algorithmvector

我有几次用它编写代码,回想起来,使用原生数组可能会更好。但在所有这些情况下,a 或 a 都会比它们中的任何一个更好。vectorBoost::multi_arrayBlitz::Array

评论

9赞 Matthieu M. 6/24/2011
int a[5];是完美的例外安全。您确定 OP 在谈论动态分配的数组吗?
0赞 Matthieu M. 6/24/2011
我勾选了灵活的,在 C 代码中通常在方法的开头分配固定长度的缓冲区。我确实认为(或)是更好的选择,尽管:)std::vectorstd::string
3赞 Perception 6/24/2011 #3

在现实世界中,这种情况很少见,因为您处理的是已知大小的固定集合。在几乎所有情况下,程序中将容纳多大的数据集都存在一定程度的未知数。事实上,一个好的程序的标志是它可以适应各种可能的情况。

以这些(琐碎的)场景为例:

  • 您已经实现了视图 控制器跟踪 AI 战斗人员 a 转数快。游戏逻辑生成随机 各区战斗人员人数 每隔几秒钟。玩家 正在以一定的速度击落人工智能战斗人员 仅在运行时已知。
  • 一名律师已进入市政当局 他所在州的法院网站,并且是 查询新酒驾案件数量 那是一夜之间进来的。他 选择按集合筛选列表 包括时间在内的变量 发生事故、邮政编码和 逮捕人员。
  • 操作系统需要 维护内存地址列表 由各种程序使用 在上面运行。程序数 以及它们的内存使用量在 不可预测的方式。

在任何这些情况下,都可以提出一个很好的论点,即可变大小列表(容纳动态插入和删除)将比简单的数组执行得更好。主要好处是减少了在添加或删除固定数组时为固定数组分配/释放内存空间的需求。

28赞 Tony Delroy 6/24/2011 #4

答:几乎总是[使用向量而不是数组]。向量既高效又灵活。它们确实需要比阵列更多的内存,但这种权衡几乎总是值得的。

这过于简单化了。使用数组是相当普遍的,并且在以下情况下可能很有吸引力:

  • 这些元素在编译时被指定,例如,const char project[] = "Super Server";const Colours colours[] = { Green, Yellow };

    • 使用 C++11 使用值初始化 s 同样简洁std::vector

  • 元素的数量本质上是固定的,例如,const char* const bool_to_str[] = { "false", "true" };Piece chess_board[8][8];

  • 首次使用性能至关重要:使用常量数组,编译器通常可以将完全预初始化对象的内存快照写入可执行映像,然后直接进行页面故障以备使用,因此通常比运行时堆分配 () 和序列化对象构造要快得多new[]

    • 编译器生成的数据表始终可以被多个线程安全地读取,而在运行时构造的数据必须在构造函数触发的其他代码尝试使用该数据之前完成构造:您最终需要某种形式的单例(可能是线程安全,它会更慢)conststatic

    • 在 C++03 中,使用初始大小创建的 s 将构造一个原型元素对象,然后复制构造每个数据成员。这意味着,即使对于故意将构造保留为无操作的类型,复制数据元素仍然需要付出代价 - 复制它们留在内存中的任何垃圾值。显然,未初始化的元素数组速度更快。vector

  • C++ 的一个强大功能是,通常您可以编写一个(或)来精确地模拟特定协议所需的内存布局,然后将类指针对准您需要使用的内存,以便方便地解释或分配值。无论好坏,许多此类协议通常嵌入小型固定大小的阵列。classstruct

  • 有一个几十年前的技巧,将一个 1 个元素的数组(如果你的编译器允许它作为扩展,甚至 0)放在结构/类的末尾,将指向结构体类型的指针定位在某个更大的数据区域,并根据对内存可用性和内容的先验知识(如果在写入之前读取)访问结构末尾的数组元素 - 请参阅零元素数组的需求是什么?

  • 包含数组的类/结构仍然可以是 POD 类型

  • 数组有助于从多个进程访问共享内存(默认情况下,指向实际动态分配数据的内部指针不会在共享内存中或跨进程有意义,并且即使指定自定义分配器模板参数,也很难强制 C++03 s 使用这样的共享内存)。vectorvector

  • 嵌入数组可以本地化内存访问要求,提高缓存命中率,从而提高性能

也就是说,如果使用 (在代码简洁性、可读性或性能方面)不是一个积极的痛苦,那么你最好这样做:他们已经 ,通过 、迭代器、调整大小(随着应用程序的“成熟”,这通常变得必要)等。如果需要,从其他标准容器更改为其他容器通常也更容易,并且应用标准算法更安全/更容易(比任何一天都好)。vectorsize()at()vectorx.end()x + sizeof x / sizeof x[0]

更新:C++11 引入了一个 ,它避免了 s 的一些成本 - 内部使用固定大小的数组来避免额外的堆分配/释放 - 同时提供了一些好处和 API 功能: http://en.cppreference.com/w/cpp/container/arraystd::array<>vector

4赞 zvrba 6/24/2011 #5

经验法则:如果您事先不知道元素的数量,或者如果预计元素数量很大(例如,超过 10 个),请使用向量。否则,您也可以使用数组。例如,我编写了大量的几何处理代码,并将一条线定义为 2 个坐标的 ARRAY。一条线由两个点定义,并且它总是由两个点定义。使用向量而不是数组在很多方面都是矫枉过正的,在性能方面也是如此。

另一件事:当我说“数组”时,我实际上是指数组:使用数组语法声明的变量,例如如果您考虑在向量和动态分配的内存块之间进行选择,例如 ,答案很明确:使用向量!int evenOddCount[2];int *evenOddCount = new int[2];

评论

0赞 Robᵩ 6/24/2011
我不会使用数组,即使在您描述的有限情况下也是如此。你如何将一个分配给另一个?你如何返回一个?你如何传递一个按值?如果是一个对象,所有这些都变得很明显,如果是一个数组,则非常令人困惑。linelinelinelineline
1赞 zvrba 6/25/2011
@Rob:中文也会让不懂中文的人感到困惑。无论如何,只有最低级别的例程才能处理此表示,然后通过指针操作所有内容。像魅力一样工作。
0赞 Tony Delroy 12/16/2013
@Rob: ;。将一行分配给另一行? ;。返回一行? ;。按值传递一行? / .使用数组并不排除包装它。struct Line { Point p[2]; }; Line l1, l2; ...;l2 = l1;return l2;void f(Line l); f(l1);
5赞 Zachary Kraus 3/10/2014 #6

我将在这里提出我的意见,以编码科学和工程中使用的大型数组/向量。

在这种情况下,基于指针的数组可能会快得多,尤其是对于标准类型。但这些指针增加了可能的内存泄漏的危险。这些内存泄漏可能导致更长的调试周期。此外,如果要使基于指针的数组动态化,则必须手动编写代码。

另一方面,对于标准类型,向量速度较慢。它们也是动态的和内存安全的,只要您不在 stl 向量中存储动态分配的指针即可。

在科学和工程领域,选择取决于项目。速度与调试时间有多重要?例如,LAAMPS 是一种模拟软件,它使用通过其内存管理类处理的原始指针。速度是该软件的首要任务。我正在构建的软件,我必须平衡速度、内存占用和调试时间。我真的不想花很多时间调试,所以我正在使用 STL 向量。

我想在这个答案中添加更多的信息,这些信息是我从对大规模阵列的广泛测试和大量阅读网络中发现的。因此,stl 向量和大型数组(100 万 +)的另一个问题发生在如何为这些数组分配内存上。Stl vector 使用 std::allocator 类来处理内存。此类是基于池的内存分配器。在小规模加载下,基于池的分配在速度和内存使用方面非常有效。随着向量的大小达到数百万,基于池的策略变成了内存消耗。发生这种情况是因为池倾向于始终保留比 stl 向量当前使用的空间更多的空间。

对于大规模向量,您最好编写自己的向量类或使用指针(原始或来自 boost 或 c++ 库的某种内存管理系统)。这两种方法各有利弊。选择实际上取决于您正在解决的确切问题(此处无法添加太多变量)。如果您碰巧编写了自己的向量类,请确保允许向量以一种简单的方法来清除其内存。目前,对于 Stl 向量,您需要使用交换操作来执行一些实际上应该首先构建到类中的事情。

评论

1赞 Assimilater 6/29/2016
这个答案也被低估了(关于大数字的一点是一个很好的观点!!)
0赞 James LT 10/31/2018
来自科学和工程背景的好建议!
0赞 Geek 12/23/2015 #7

就数组而言,简单的整数或字符串数组非常易于使用。另一方面,对于搜索、排序、插入、删除等常见功能,您可以在向量上使用标准算法(内置库函数)实现更快的速度。特别是如果您使用对象的向量。 其次,存在一个巨大的差异,即随着插入更多对象,向量的大小可以动态增长。希望能有所帮助。

评论

0赞 M.M 12/23/2015
搜索和排序可以对 C 样式数组进行操作,也可以对向量进行操作