在C++中,应避免使用使用new
创建的动态数组(即使用动态数组), 因为你需要跟踪其大小并手动删除它们, 还需要进行各种繁琐的操作。
使用栈上的数组也不建议,因为你没有范围检查,并且传递该数组将丢失关于其大小的任何信息(数组到指针的转换)。这种情况下,应该使用std::array
,它将C++数组封装在一个小类中,并提供了size
函数和迭代器来遍历它。
现在,std::vector与原生C++数组之间的区别(取自互联网):
// Comparison of assembly code generated for basic indexing, dereferencing,
// and increment operations on vectors and arrays/pointers.
// Assembly code was generated by gcc 4.1.0 invoked with g++ -O3 -S on a
// x86_64-suse-linux machine.
#include <vector>
struct S
{
int padding;
std::vector<int> v;
int * p;
std::vector<int>::iterator i;
};
int pointer_index (S & s) { return s.p[3]; }
// movq 32(%rdi), %rax
// movl 12(%rax), %eax
// ret
int vector_index (S & s) { return s.v[3]; }
// movq 8(%rdi), %rax
// movl 12(%rax), %eax
// ret
// Conclusion: Indexing a vector is the same damn thing as indexing a pointer.
int pointer_deref (S & s) { return *s.p; }
// movq 32(%rdi), %rax
// movl (%rax), %eax
// ret
int iterator_deref (S & s) { return *s.i; }
// movq 40(%rdi), %rax
// movl (%rax), %eax
// ret
// Conclusion: Dereferencing a vector iterator is the same damn thing
// as dereferencing a pointer.
void pointer_increment (S & s) { ++s.p; }
// addq $4, 32(%rdi)
// ret
void iterator_increment (S & s) { ++s.i; }
// addq $4, 40(%rdi)
// ret
// Conclusion: Incrementing a vector iterator is the same damn thing as
// incrementing a pointer.
注意:如果你使用 new
分配数组,并分配非类对象(如普通的 int
)或没有用户定义构造函数的类 并且 不想在初始时初始化元素,则使用 new
分配的数组可能具有性能优势,因为 std::vector
在构造时会将所有元素初始化为默认值(例如 int 为 0)(感谢 @bernie 提醒)。
记住:
“程序员浪费了大量时间思考或担心程序中非关键部分的速度,而这些效率尝试在考虑调试和维护时实际上会产生强烈的负面影响。我们应该忘记小的效率,大约97%的时间:过早地进行优化是万恶之源。然而,在关键的3%机会中,我们不应该放弃。”
(感谢 metamorphosis 提供完整引用)
不要仅仅因为你认为它更低级别就使用 C 数组而不是 vector(或其他容器)。你会错的。
默认使用 vector(或适合您需求的安全容器),如果您的分析器说它是个问题,请尝试优化它,可以通过使用更好的算法或更改容器来实现。
话虽如此,我们可以回到最初的问题。
C++ 数组类比低级别的 C 数组表现更好,因为它们对自身有很多了解,并且可以回答 C 数组无法回答的问题。它们能够在自己后清理自己。更重要的是,它们通常使用模板和/或内联编写,这意味着在调试中出现大量代码,在发布版本中不会产生任何代码,与其内置的不太安全的竞争没有区别。
总而言之,它分为两类:
使用指向malloc或new分配的数组的指针速度最快与std::vector版本相同,并且不太安全(请参见litb's post)。
因此,请使用std::vector。
使用静态数组速度最快:
因此,请使用std::array。
有时,使用vector
而不是原始缓冲区会产生可见的成本,因为vector
将在构建时初始化缓冲区,而替换它的代码没有,如bernie在他的answer中所述。
如果是这种情况,则可以通过使用unique_ptr
而不是vector
来处理它,或者如果该情况在您的代码行中不是异常情况,则实际编写一个类buffer_owner
,它将拥有该内存,并为您提供易于使用和安全的访问,包括诸如调整大小(使用realloc
?)或其他所需内容的奖励。
向量的本质是数组。
性能是相同的。
一个可能导致性能问题的地方是没有正确地给向量分配大小。
当向量被填满时,它将重新调整大小,这会导致新的数组分配,接着大约n个复制构造函数、n个析构函数调用,以及一个数组删除。
如果你的构造/析构比较昂贵,最好一开始就设置向量的正确大小。
有一种简单的方法来演示这一点。创建一个简单的类,展示它何时被构造/销毁/复制/赋值。创建一个这些类的向量,并开始将它们推到向量的后端。当向量被填满时,会出现一系列的活动,因为向量重新调整大小。然后再试一次,将向量大小设置为预期的元素数量。你会看到差异。
std::vector
是否符合标准呢?我相信标准要求vector::push_back
具有平均常数复杂度,而在每个push_back
上增加容量1将会在考虑到重新分配后导致n^2的复杂度。假设在push_back
和insert
上采用某种指数级的容量增长方式,则未执行reserve
将最多导致向量内容副本的恒定因子增加。如果使用1.5的指数级向量增长因子,则如果未执行reserve()
,则需要进行大约3倍的副本操作。 - Yakk - Adam Nevraumont针对Mehrdad的观点做出回应:
然而,在某些情况下,你仍然需要数组。当与低级代码(比如汇编)或要求使用数组的旧库进行交互时,你可能无法使用向量。
这并不完全正确。如果你使用以下语法,向量可以很好地退化为数组/指针:
vector<double> vector;
vector.push_back(42);
double *array = &(*vector.begin());
// pass the array to whatever low-level code you have
这适用于所有主要的STL实现。在下一个标准中,将需要它工作(即使它今天表现得足够好)。
n
在数组大小范围内,就可以使用 &v[n] == &v[0] + n
这个表达式。在 C++11 中,包含这个声明的段落并没有改变。 - bjhendstd::vector<double>
,你可以将其传递给期望 size_t width, double arr2d[width][]
的 C 接口。当然,在没有可变长度数组(VLA)的 C++ 实现中,第二个参数实际上必须声明为 void*
或 double*
等,只有在 C99 代码中才能看到二维数组,因为你不能将 vec.data()
强制转换为指向可变长度数组的指针。 - Peter Cordes在C++11中,使用普通数组的理由更少了。
从速度最快到最慢,自然界中有三种类型的数组,取决于它们具有的特性(当然,实现质量可以让列表中的第三种情况非常快):
std::array<T, N>
dynarray
。在C语言中有可变长度数组std::vector<T>
对于1.具有固定元素数的普通静态数组,在C++11中使用std::array<T, N>
。
对于2.在运行时指定的固定大小数组,但它们的大小不会发生变化,C++14中存在讨论,但已将其移动到技术规范,并最终从C++14中删除。
对于3.std::vector<T>
,通常会在堆上请求内存。虽然这可能会产生性能影响,但您可以使用std::vector<T, MyAlloc<T>>
来改善情况,使用自定义分配器。与T mytype[] = new MyType[n];
相比的优点是,您可以调整其大小,并且它不会像普通数组一样衰减为指针。
使用提到的标准库类型以避免数组衰减为指针。您将节省调试时间,并且如果使用相同的功能集,则性能与普通数组完全相同。
std::vector
与使用原始数组时,当您需要一个未初始化的缓冲区(例如,用作memcpy()
的目标)时,肯定会对性能产生影响。一个std::vector
将使用默认构造函数初始化其所有元素,而原始数组则不会。count
参数的std:vector
构造函数(第三种形式)的C++规范如下所述:
“从各种数据源构造一个新的容器,可选择使用用户提供的分配器alloc。”
- 使用count个默认插入的T实例构造容器。不进行任何复制。
复杂度
原始数组不会产生这种初始化成本。2-3)与count成线性关系
std::vector
将始终进行值构造,在一些边缘情况下可能会有轻微的开销。在您引用的构造函数中,向量进行了值构造,尽管暗示它进行了默认构造,这非常令人恼火。 - Mooing Duck选择使用STL。这样做不会影响性能。STL中的算法非常高效,并且可以很好地处理我们大多数人没有考虑到的细节。
STL是一个经过高度优化的库。事实上,甚至建议在需要高性能的游戏中使用STL。数组在日常任务中使用也太容易出错了。今天的编译器也非常聪明,可以用STL真正产生出色的代码。如果您知道自己在做什么,STL通常可以提供必要的性能。例如,通过将向量初始化为所需大小(如果您从一开始就知道),您基本上可以实现数组性能。但是,在与低级代码(即汇编语言)或需要数组的旧库进行交互时,您可能无法使用向量。
vec.data()
用于数据,vec.size()
用于大小。就是这么简单。 - Germán Diago
int main(int argc, const std::vector<string>& argv)
。 - Mark K Cowan