C++ Primer, 5th edition(ISBN 0-321-71411-3 / 978-0-321-71411-4)的书 Stanley B. Lippman 提到:
[std::]array
是一种更安全,更易于使用的内置数组替代方案。
内置数组有什么问题?
答案 0 :(得分:29)
内置数组是一个连续的字节块,通常在堆栈上。你真的没有办法保存关于数组,它的边界或状态的有用信息。 std::array
保留此信息。
内置数组在从/向函数传递时会被衰减为指针。这可能会导致:
传递内置数组时,传递原始指针。指针不保留有关数组大小的任何信息。您将不得不传递数组的大小,从而uglify代码。 std::array
可以作为参考,复制或移动传递。
无法返回内置数组,如果在该函数范围内声明了数组,最终将返回指向局部变量的指针。
std::array
可以安全返回,因为它是一个对象,它的生命周期是自动管理的。
你无法在内置数组上做有用的事情,比如分配,移动或复制它们。您将结束为每个内置数组编写自定义函数(可能使用模板)。可以分配std::array
。
通过访问数组边界之外的元素,您将触发未定义的行为。如果检查失败,std::array::at
将执行边界检查并抛出常规C ++异常。
更好的可读性:内置数组涉及指针算术。 std::array
实现了有用的功能,例如front
,back
,begin
和end
以避免这种情况。
假设我想对内置数组进行排序,代码可能如下:
int arr[7] = {/*...*/};
std::sort(arr, arr+7);
这不是有史以来最强大的代码。通过将7
更改为其他数字,代码会中断。
使用std::array
:
std::array<int,7> arr{/*...*/};
std::sort(arr.begin(), arr.end());
代码更加强大和灵活。
为了清楚起见,内置数组有时会更容易。例如,许多Windows以及UNIX API函数/系统调用都需要一些(小)缓冲区来填充数据。我不会使用std::array
的开销而不是我可能正在使用的简单char[MAX_PATH]
。
答案 1 :(得分:25)
很难判断作者的意思,但我猜他们指的是关于原生数组的以下事实:
他们是原始的
没有.at
成员函数可以用于带边界检查的元素访问,尽管我反击你通常不想要。要么您正在访问您知道存在的元素,要么您正在迭代(您可以使用std::array
和本机数组进行同样的处理);如果你不知道元素是否存在,那么边界检查访问器已经是一种很难确定的方法,因为它使用了错误的代码流工具,并且会带来很大的性能损失。
他们可能会混淆
新手倾向于忘记数组名称衰减,将数组传递给函数&#34;按值&#34;然后对随后的指针执行sizeof
;这通常不是'#34;不安全&#34;,但它会造成错误。
无法分配
同样,本质上不安全,但它会导致愚蠢的人们用多级指针和大量动态分配编写愚蠢的代码,然后失去记忆并犯下各种UB罪行。
假设作者推荐std::array
,那将是因为它&#34;修复&#34;所有上述内容,默认情况下通常会有更好的代码。
但本机阵列本质上是不安全的&#34;不安全&#34;通过比较?不,我不会这么说。
答案 2 :(得分:6)
std::array
如何比内置数组更安全,更易于使用?
很容易陷入内置数组,特别是对于那些不是C ++专家和程序员的程序员,他们有时会犯错误。这会导致许多错误和安全漏洞。
std::array a1
,您可以访问边界检查a.at(i)
或没有边界检查a[i]
的元素。使用内置数组,始终您有责任努力避免越界访问。否则,代码可能会粉碎一些长时间未被注意的内存,并且变得非常难以调试。 即使只是在数组边界外读取也可以利用安全漏洞,例如泄漏私有加密密钥的Heartbleed漏洞。std::array a1
,您可以获取其大小a1.size()
,将其内容与另一个std :: array a1 == a2
进行比较,并使用其他标准容器方法,如a1.swap(a2)
。使用内置数组,这些操作需要更多的编程工作,并且更容易搞乱。例如。如果int b1[] = {10, 20, 30};
获得其大小而没有硬编码3,则必须执行sizeof(b1) / sizeof(b1[0])
。要比较它的内容,你必须遍历这些元素。f(&a1)
或值f(a1)
将std :: array传递给函数[即通过复制]。传递内置数组只能通过引用进行处理,并使用指向第一个元素的指针将其混淆。那不是一回事。编译器不传递数组大小。return a1
从函数返回std :: array。返回一个内置数组return b1
会返回一个悬空指针,该指针已被破坏。a1 = a2
复制一个std :: array,即使它包含带有构造函数的对象。如果你尝试使用内置数组b1 = b2
,它只会复制数组指针(或者无法编译,具体取决于b2
的声明方式)。您可以使用memcpy(b1, b2, sizeof(b1) / sizeof(b1[0]))
来解决这个问题,但如果数组具有不同的大小或者它们包含带有构造函数的元素,则会破坏它。请参阅C ++常见问题解答Why should I use container classes rather than simple arrays?以了解详情,例如:包含具有析构函数(如std::string
)或继承的C ++对象的内置数组的危险。
边界检查访问a1.at(i)
需要更多指令。在一些内部循环代码中,这些代码会阻塞大型数组(例如,您在每个视频帧上调用的图像处理例程),这个成本可能会加起来很重要。在极少数情况下,使用未经检查的访问a[i]
和是有意义的,请仔细确保循环代码处理边界。
在大多数代码中,您要么将图像处理代码卸载到GPU,要么边界检查成本只占总运行时间的一小部分,或者整体运行时间不是问题。同时,阵列访问错误的风险很高,从调试它的时间开始。
答案 3 :(得分:5)
内置数组的唯一好处是更简洁的声明语法。但是std::array
的功能性好处使其脱离水中。
我还要补充一点,这并不重要。如果你必须支持较旧的编译器,那么你当然没有选择,因为std::array
仅适用于C ++ 11。否则,你可以使用你喜欢的任何一个,但除非你只是简单地使用数组,你应该更喜欢std :: array只是为了让事情与其他STL容器保持一致(例如,如果你以后决定使大小动态,那该怎么办? ,并使用std :: vector,然后你会很高兴你使用std::array
因为所有你必须改变的可能是数组声明本身,其余的将是相同的,特别是如果你使用自动和C ++ 11的其他类型推断功能。
std::array
是一个模板类,它封装了一个存储在对象本身内的静态大小的数组,这意味着,如果在堆栈上实例化该类,则数组本身将位于堆栈中。它的大小必须在编译时知道(它作为模板参数传递),并且它不能增长或缩小。
数组用于存储一系列对象 查看教程:http://www.cplusplus.com/doc/tutorial/arrays/
std :: vector做的相同,但它比内置数组更好(例如:一般来说,vector的效率差别不大于内置数据 在通过operator []访问元素时在数组中:http://www.cplusplus.com/reference/stl/vector/
内置数组是错误的主要来源 - 尤其是当它们用于构建多维数组时。 对于新手来说,他们也是混乱的主要来源。尽可能使用vector,list,valarray,string等。 STL容器与内置数组没有相同的问题
因此,C ++中没有理由坚持使用内置数组。内置数组采用C ++,主要是为了向后兼容C。
如果OP真的想要一个数组,C ++ 11为内置数组std :: array提供了一个包装器。使用std :: array非常类似于使用内置数组对其运行时性能没有影响,具有更多功能。
与标准库中的其他容器不同,交换两个数组容器是一种线性操作,涉及单独交换范围中的所有元素,这通常是一种效率相当低的操作。另一方面,这允许两个容器中的元素的迭代器保持其原始容器关联。 数组容器的另一个独特特性是它们可以被视为元组对象:头部重载get函数以访问数组的元素,就像它是一个元组一样,以及专门的tuple_size和tuple_element类型。
无论如何,内置数组都是通过引用传递的方式。这样做的原因是当你将一个数组作为参数传递给一个函数时,会传递指向它的第一个元素的指针。
当你说void f(T[] array)
编译器会把它变成void f(T* array)
时
说到字符串。 C风格的字符串(即空终止字符序列)都是通过引用传递的方式,因为它们也是'char'数组。
默认情况下,STL字符串不会通过引用传递。它们就像正常变量一样。 没有用于通过引用传递参数的预定义规则。即使数组总是自动通过引用传递。
vector<vector<double>> G1=connectivity( current_combination,M,q2+1,P );
vector<vector<double>> G2=connectivity( circshift_1_dexia(current_combination),M,q1+1,P );
这也可能是复制向量,因为连接按值返回向量。在某些情况下,编译器会对此进行优化。但要确保避免这种情况,您可以将向量作为非const引用传递给连接而不是返回它们。 maxweight的返回值是一个由值返回的三维向量(可以复制它)。 向量仅在最后插入或擦除时有效,如果要推送大量值,最好调用reserve()。如果您不需要随机访问,您可以使用列表重新编写它;使用list会丢失下标运算符,但仍然可以进行线性传递,并将迭代器保存到元素而不是下标。
对于某些编译器,使用预增量而不是后增量可能更快。除非你真的需要使用后增量,否则首选++ i到i ++。它们不一样。
无论如何,如果你没有编译优化,矢量会非常慢。通过优化,它接近内置数组。内置数组也可能非常慢,没有优化,但没有矢量那么糟糕。
答案 4 :(得分:2)
std::array具有at
成员函数,这是安全的。它还有begin
,end
,size
,您可以使用它来使代码更安全。
原始数组没有。 (特别是,当原始数组在作为参数传递时被衰减为指针-eg时,您将丢失任何大小信息,这些信息保存在std::array
类型中,因为它是一个大小为参数的模板)
优秀的C ++ 11编译器将像原始数组一样有效地处理std::array
(或对它们的引用)。
答案 5 :(得分:2)
内置数组本质上不安全 - 如果使用正确。但是错误地使用内置数组比使用std::array
等替代方法更容易,这些替代方法通常会提供更好的调试功能,以帮助您检测错误使用时间。
答案 6 :(得分:2)
内置数组非常精细。即使是有经验的程序员,也有很多方面表现得出乎意料。
std::array<T, N>
确实是T[N]
的包装器,但许多已经提到的方面基本上是免费的,这是非常理想的,也是你想要的。
这些是我还没读过的:
大小: N
应该是一个常量表达式,对于两者都不能是变量。 但是,内置数组,还有VLA(可变长度数组)也允许这样做。
官方说来,只有C99支持它们。尽管如此,许多编译器仍允许在以前版本的C和C ++中使用extensions。因此,你可能有
int n; std::cin >> n;
int array[n]; // ill-formed but often accepted
compiles fine。如果您使用std::array
,则无法使用,因为N
是必需的并且已检查是实际的常量表达式!
对阵列的期望:一个常见的缺陷是,当阵列不再是一个阵列时,阵列的大小随阵列本身一起携带误导C语法,如:
void foo(int array[])
{
// iterate over the elements
for (int i = 0; i < sizeof(array); ++i)
array[i] = 0;
}
但这是错误的,因为array
已经衰减到指针,指针没有关于指向区域大小的信息。如果array
具有少于sizeof(int*)
个元素(通常为8个),那么误解会触发未定义的行为,除了在逻辑上是错误的。
疯狂使用:更进一步,有一些奇怪的阵列有:
Whether you have array[i] or i[array],没有区别。对于array
,情况并非如此,因为调用重载运算符实际上是函数调用,参数的顺序很重要。
零大小的数组:N
应大于零但仍然允许它作为扩展名,并且像以前一样,除非需要更多的迂腐,否则通常不会发出警告。更多信息here。
array
有不同的semantics:
零长度数组
(N == 0)
有一种特殊情况。在那里面 case,array.begin() == array.end()
,这是一个独特的值。该 在零大小的数组上调用front()
或back()
的效果是 未定义。