现在我们有了std::array,那么还有哪些情况需要使用C风格数组呢?

90

std::array比C数组优越得多。即使我想与旧代码进行交互,我也可以使用std::array::data()。有没有任何理由我会想要老式数组?


请注意,<array>是一个很重的标准库头文件,可以大大增加编译时间(因标准库实现而异),而C风格的数组不需要任何包含/导入,因为它是核心语言/语法的一部分。 - Matthias
7个回答

61

除非我漏掉了什么(我没有过于密切地关注最近的标准变化),否则大多数C风格数组的用法仍然存在。 std :: array 确实允许静态初始化,但它仍然不会为您计算初始化程序。而且由于在std :: array 之前,C风格数组唯一真正的用途是进行静态初始化的类似表格的操作。

MyStruct const table[] =
{
    { something1, otherthing1 },
    //  ...
};

使用通常的beginend模板函数(在C++11中采用)来迭代它们。 从初始化器的数量中推断大小,而无需提及大小。

编辑:我忘记的另一件事:字符串字面量仍然是C样式数组;即类型为char[]。 我不认为任何人会因为我们有了std::array就排除使用字符串字面量。


7
你可以编写一个可变参数函数模板,用它可以构造数组而无需指定长度。 - user1804599
2
在C++17中支持类模板推导自动推导初始化器数量。例如,“auto a = std::array {1, 2, 3};” - Ricky65
吹毛求疵:字符串字面量的类型是const char [] - Bulletmagnet
自从 C++17,你可以使用 std::array a4{3.0, 1.0, 4.0}; // 它会给你 std::array<double, 3> (来源:https://en.cppreference.com/w/cpp/container/array 页面末尾的代码)。 - kingsjester

33

不需要。简而言之,用30个字符表示。

当然,实现std::array需要使用C数组,但这并不是用户想要C数组的理由。此外,std::array不比C数组性能差,并且还有一种选项可以进行边界检查。最后,任何C++程序都可以依赖于标准库-这就是它成为标准的原因-如果您无法访问标准库,则您的编译器不符合标准,问题标签为"C++",而不是"C++和那些遗漏了一半规范的非C++东西,因为他们觉得不合适"。


1
嗯,如果你正在编写被其他语言调用并需要传递参数的C++代码呢? - asveikau
3
自由实现可以省略几乎所有标准库而仍然符合规范。不过,如果一个自由实现的 C++11 编译器不能实现 std::array,我会对其产生严重的怀疑。 - Dennis Zickefoose
12
C++0x标准草案(文件N3092)§1.4.7中提到定义了两种实现方式:托管和独立的。对于托管实现,这个国际标准定义了一组可用库。独立的实现是指可以在没有操作系统的情况下执行的实现,并且有一个由实现定义的一组库,其中包括某些语言支持库。STL不被视为"语言支持"库,在独立编译器中不包括它。 - Earlz

25

看起来使用 C 数组比使用 std::array 更容易实现多维数组。例如,

char c_arr[5][6][7];

相比之下

std::array<std::array<std::array<char, 7>, 6>, 5> cpp_arr;

此外,由于C数组的自动衰减特性,在上面的示例中c_arr[i]将衰减为指针,你只需要将其余维度作为另外两个参数传递即可。我的观点是,复制c_arr并不昂贵。然而,复制cpp_arr[i]将非常昂贵。


32
一个简单的 template <typename T, int M, int N> using array2d = std::array<std::array<T, N>, M>; 应该可以解决任何这些问题。 - Miles Rout
8
你的示例 c_arr 复制起来非常耗费资源!你必须自己提供复制代码。它将会衰减为指针,更接近于引用而不是副本,如果你想传递引用,可以使用 std::array - ChetS
2
@MilesRout 从技术上讲,这应该是std::size_t而不是int吧?很抱歉挑剔了一下,但这会使它通用。 - robbie
1
@robbie0630 是的,如果你想的话,可以将其设置为 size_t。虽然我无法想象有多少情况需要具有超过40亿行或列的数组。 - Miles Rout
1
@MilesRout,在某些架构上,int 可以是 16 位。是的,您可以拥有超过 32768 个元素的数组。在相同的架构上,size_t 通常是 32 位且为无符号类型。 - xryl669
显示剩余2条评论

15

正如Sumant所说,使用内置的C数组比使用std::array更容易使用多维数组。

当嵌套时,std::array可能会变得非常难以阅读且不必要地冗长。

例如:

std::array<std::array<int, 3>, 3> arr1; 

相较于

char c_arr[3][3]; 
此外,请注意,在嵌套 std::array 时, begin() end() size()都会返回无意义的值。
因此,出于这些原因,我创建了自己的固定大小多维数组容器 array_2d array_3d 。它们类似于 std::array ,但用于二维和三维的多维数组。它们比内置的多维数组更安全,性能也不差。我没有包含大于3维的多维数组容器,因为这种情况很少见。在C++0x中,可以制作可变参数模板版本,以支持任意数量的维度。
下面是二维数组的示例:
//Create an array 3 x 5 (Notice the extra pair of braces) 

fsma::array_2d <double, 3, 5> my2darr = {{
    { 32.19, 47.29, 31.99, 19.11, 11.19},
    { 11.29, 22.49, 33.47, 17.29, 5.01 },
    { 41.97, 22.09, 9.76, 22.55, 6.22 }
}};

完整的文档在此处可用:

http://fsma.googlecode.com/files/fsma.html

您可以在此处下载该库:

http://fsma.googlecode.com/files/fsma.zip


4
固定大小的C风格数组很容易,但如果您想要改变维度,则会变得复杂。例如,对于给定的arr[x][y],您无法确定arr是数组的数组、指针的数组、数组的指针还是指向指针的指针;所有这些实现都是合法的,具体取决于您的需求。而且,多数实际应用场景下需要在运行时确定数组的大小。 - Keith Thompson
我很想看到那个n维数组的可变参数模板实现!元编程的最佳实践! - steffen
3
我在几年前曾经尝试过,你可以在这里查看:https://code.google.com/p/fsma/source/browse/trunk/variadic_template_array.hpp。测试用例在这里:https://code.google.com/p/fsma/source/browse/trunk/variadic_template_array_test.cpp。虽然我相信它可以做得更好。 - Ricky65

5

在C++中可用的C风格数组实际上比真正的C数组要不太灵活。区别在于,在C中,数组类型可以具有运行时大小。以下是有效的C代码,但它既不能用C++ C风格数组表示,也不能用C++ array<>类型表示:

void foo(int bar) {
    double tempArray[bar];
    //Do something with the bar elements in tempArray.
}

在 C++ 中,你需要在堆上分配临时数组:
void foo(int bar) {
    double* tempArray = new double[bar];
    //Do something with the bar elements behind tempArray.
    delete[] tempArray;
}

这不能用 std::array<> 实现,因为编译时不知道 bar 的大小,需要使用C++的 C 风格数组或std::vector<>。虽然第一个示例可以相对容易地在C++中表达(尽管需要使用 new[]delete[]),但是以下内容如果不使用 std::vector<> 就无法在C++中实现。
void smoothImage(int width, int height, int (*pixels)[width]) {
    int (*copy)[width] = malloc(height*sizeof(*copy));
    memcpy(copy, pixels, height*sizeof(*copy));
    for(y = height; y--; ) {
        for(x = width; x--; ) {
            pixels[y][x] = //compute smoothed value based on data around copy[y][x]
        }
    }
    free(copy);
}

问题在于,在C++中,指向行数组 int (*)[width] 的指针不能使用运行时的宽度,这使得任何图像处理代码在C++中比在C语言中更加复杂。一个典型的C++实现图像处理示例的代码如下:

void smoothImage(int width, int height, int* pixels) {
    int* copy = new int[height*width];
    memcpy(copy, pixels, height*width*sizeof(*copy));
    for(y = height; y--; ) {
        for(x = width; x--; ) {
            pixels[y*width + x] = //compute smoothed value based on data around copy[y*width + x]
        }
    }
    delete[] copy;
}

这段代码与上面的 C 代码做完全相同的计算,但需要手动进行索引计算在使用索引的任何地方。对于二维情况,这仍然是可行的(尽管有很多机会错误地进行索引计算)。但在三维情况下,情况变得非常糟糕。
我喜欢用 C++ 编写代码。但每当我需要操作多维数据时,我真的会考虑是否应该将代码的这部分移至 C 中。

7
需要注意的是,至少Clang和GCC在C++中支持VLA(可变长数组)。 - Janus Troelsen
@JanusTroelsen,而且它们在支持的元素类型方面非常有限。 - user1804599
C11不是使VLA成为可选项吗?如果是这样,那么我认为你的答案是误导性的。当C99是标准时它是正确的,但不是在C11中。 - Z boson
1
@Zboson C99是C标准,有一些编译器实现了其VLA功能(例如gcc)。C11使一些有趣的东西变成可选项,我认为这不是因为他们想禁止该功能。我倾向于把它看作是他们想降低编写符合完全标准的编译器所需的级别的迹象:VLA是非常难以实现的,很多代码可以不用,因此对于某个新平台上的新编译器而言,不必立即实现VLA是有意义的。 - cmaster - reinstate monica
可变长度数组(VLAs)在c++14标准中被添加,但很快被移除。G++和Clang++自2005年左右就拥有了VLAs,并且现在仍然保留。(主要是MSVC拒绝实现它们;也许一些独立编译器会有问题。)我对C ++原始数组的主要不满是,2D数组无法从列表中同时初始化行和列大小,因此您需要手动计算c-字符串初始化列表中每个项目的长度。 - Max Power

-1

也许 std::array 并不慢。但是我使用简单的存储和从 std::array 读取进行了一些基准测试; 请参阅以下基准测试结果(在 W8.1、VS2013 Update 4 上):

ARR_SIZE: 100 * 1000
Avrg = Tick / ARR_SIZE;

test_arr_without_init
==>VMem: 5.15Mb
==>PMem: 8.94Mb
==>Tick: 3132
==>Avrg: 0.03132
test_arr_with_init_array_at
==>VMem: 5.16Mb
==>PMem: 8.98Mb
==>Tick: 925
==>Avrg: 0.00925
test_arr_with_array_at
==>VMem: 5.16Mb
==>PMem: 8.97Mb
==>Tick: 769
==>Avrg: 0.00769
test_c_arr_without_init
==>VMem: 5.16Mb
==>PMem: 8.94Mb
==>Tick: 358
==>Avrg: 0.00358
test_c_arr_with_init
==>VMem: 5.16Mb
==>PMem: 8.94Mb
==>Tick: 305
==>Avrg: 0.00305

根据负面评价,我使用的代码在pastebin上(链接)。

基准测试类代码在这里

我对基准测试不是很了解...我的代码可能有缺陷。


7
没有基准测试代码或编译标志的基准测试结果?你可以做得更好。 - R. Martinho Fernandes
就这么一点代码而言,它已经显示出基准测试是严重有缺陷的。足够聪明的编译器将会把整个东西转换成 long test_arr_without_init() { return ARR_SIZE; } - R. Martinho Fernandes
那只是一个例子。我认为这没什么大不了的。我改变了代码以返回void,在VS 2013中使用了发布版本,带有/O2 /Ot /Gl。 - K'Prime
移除返回值意味着编译器现在可以将整个内容转换为 void test_arr_without_init() {}。你真的需要费尽心思来确保你要测量的代码是你想要测量的代码。 - R. Martinho Fernandes

-5
  1. 实现类似于 std::array 的东西
  2. 如果您不想使用STL或无法使用
  3. 出于性能考虑

27
请告诉我,相比于 C 数组,std::array 会导致性能下降的原因。 - Xeo
19
@Aaron McDaid:这只存在于 at() 中,就像 std::vector 一样,并不在 operator[] 中。对于 std::array,没有性能降低或代码膨胀的问题,编译器被设计成优化这种情况。当然,添加检查函数是一个优秀的调试工具和重要的优势。@Lou Franco:所有的 C++ 代码都可能依赖于标准库,因为这就是标准库的作用。@Earlz:如果没有 STL 可用,那么就不是 C ++,这就是这个问题的结论。 - Puppy
6
@Earlz:C++标准包含标准库。如果不能使用库,则不符合规范。其次,如果使用std::array比等效的C数组用法更大,那么你必须有一个非常糟糕的编译器。 - Puppy
5
@Earlz说:“‘不完全符合’和‘缺少数百页规范中的功能’之间有很大的区别。” - Puppy
2
@DeadMG:主要是同意。但不要忘记,C++的实现可能是自由站立的,只有一组最小保证头文件(“自由站立实现[合规性]”),而所谓的STL有时不是最小头文件集的一部分。 - Sebastian Mach
显示剩余13条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接