如何在C++中使用数组?

How do I use arrays in C++?

C++从C继承数组,在那里几乎到处使用。C++提供了更易于使用和更容易出错的抽象(自从C++ 98和EDCOX1 1以来,EDCOX1是0),所以对数组的需求并不像C中那样频繁出现。然而,当你读取遗留代码或与C语言编写的库交互时,你应该牢牢掌握数组是如何工作的。

本常见问题分为五部分:

  • 类型级别上的数组和访问元素
  • 数组创建和初始化
  • 赋值和参数传递
  • 多维数组和指针数组
  • 使用数组时的常见陷阱
  • 如果您觉得本常见问题解答中缺少一些重要内容,请写一个答案并将其作为附加部分链接到此处。

    在下面的文本中,"array"表示"c array",而不是类模板std::array。假设具备C声明符语法的基本知识。请注意,下面演示的newdelete的手动使用在例外情况下非常危险,但这是另一个常见问题的主题。

    (注:这意味着是堆栈溢出的C++FAQ的一个条目。如果你想批评在这个表单中提供一个常见问题解答的想法,那么在meta上发布的开始所有这一切的地方就是这样做的地方。这个问题的答案是在C++聊天室中进行监控的,FAQ的想法一开始就出现了,所以你的答案很可能会被那些想出这个想法的人读到。


    类型级别上的数组

    数组类型表示为T[n],其中T表示元素类型,n表示数组中元素的数量。数组类型是元素类型和大小的产品类型。如果这些成分中的一种或两种不同,您会得到一种不同的类型:好的。

    1
    2
    3
    4
    #include <type_traits>

    static_assert(!std::is_same<int[8], float[8]>::value,"distinct element type");
    static_assert(!std::is_same<int[8],   int[9]>::value,"distinct size");

    请注意,大小是类型的一部分,也就是说,不同大小的数组类型是不兼容的类型,它们彼此之间完全没有任何关系。sizeof(T[n])相当于n * sizeof(T)。好的。数组到指针衰减

    T[n]T[m]之间唯一的"连接"是这两种类型都可以隐式转换为T*,转换的结果是指向数组的第一个元素的指针。也就是说,在任何需要T*的地方,您都可以提供T[n],编译器将静默地提供该指针:好的。

    1
    2
    3
    4
    5
    6
    7
    8
                      +---+---+---+---+---+---+---+---+
    the_actual_array: |   |   |   |   |   |   |   |   |   int[8]
                      +---+---+---+---+---+---+---+---+
                        ^
                        |
                        |
                        |
                        |  pointer_to_the_first_element   int*

    这种转换被称为"数组到指针的衰减",它是造成混淆的主要原因。在此过程中,数组的大小将丢失,因为它不再是类型的一部分(T*)。pro:忘记类型级别上数组的大小允许指针指向任意大小数组的第一个元素。con:给定一个指向数组第一个(或任何其他)元素的指针,就无法检测该数组有多大,或者指针指向相对于数组边界的确切位置。指针是非常愚蠢的。好的。数组不是指针

    每当认为数组的第一个元素有用时,即当操作在数组上失败但在指针上成功时,编译器将自动生成指向该元素的指针。从数组到指针的转换很简单,因为得到的指针值只是数组的地址。请注意,指针不是作为数组本身(或内存中的任何其他地方)的一部分存储的。数组不是指针。好的。

    1
    static_assert(!std::is_same<int[8], int*>::value,"an array is not a pointer");

    数组不衰减为指向其第一个元素的指针的一个重要上下文是当&运算符应用于它时。在这种情况下,&操作符生成一个指向整个数组的指针,而不仅仅是指向第一个元素的指针。尽管在这种情况下,值(地址)是相同的,但指向数组第一个元素的指针和指向整个数组的指针是完全不同的类型:好的。

    1
    static_assert(!std::is_same<int*, int(*)[8]>::value,"distinct element type");

    以下ASCII艺术解释了这一区别:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
          +-----------------------------------+
          | +---+---+---+---+---+---+---+---+ |
    +---> | |   |   |   |   |   |   |   |   | | int[8]
    |     | +---+---+---+---+---+---+---+---+ |
    |     +---^-------------------------------+
    |         |
    |         |
    |         |
    |         |  pointer_to_the_first_element   int*
    |
    |  pointer_to_the_entire_array              int(*)[8]

    请注意,指向第一个元素的指针只指向一个整数(表示为一个小方框),而指向整个数组的指针指向一个8个整数的数组(表示为一个大方框)。好的。

    同样的情况也出现在课堂上,而且可能更明显。指向对象的指针及其第一个数据成员的指针具有相同的值(相同的地址),但它们是完全不同的类型。好的。

    如果您不熟悉C声明符语法,那么类型int(*)[8]中的括号是必不可少的:好的。

    • int(*)[8]是指向8个整数数组的指针。
    • int*[8]是一个由8个指针组成的数组,每个指针都是int*类型的元素。

    访问元素

    C++提供了两个语法变量来访问数组的各个元素。两者都不优于对方,你应该熟悉两者。好的。指针算术

    给定指向数组第一个元素的指针p,表达式p+i生成指向数组第i个元素的指针。通过随后取消对该指针的引用,可以访问单个元素:好的。

    1
    std::cout << *(x+3) <<"," << *(x+7) << std::endl;

    如果x表示一个数组,那么数组到指针的衰减将开始,因为添加数组和整数毫无意义(数组上没有加号操作),但是添加指针和整数是有意义的:好的。

    1
    2
    3
    4
    5
    6
    7
    8
       +---+---+---+---+---+---+---+---+
    x: |   |   |   |   |   |   |   |   |   int[8]
       +---+---+---+---+---+---+---+---+
         ^           ^               ^
         |           |               |
         |           |               |
         |           |               |
    x+0  |      x+3  |          x+7  |     int*

    (注意,隐式生成的指针没有名称,所以我编写x+0来标识它。)好的。

    另一方面,如果x表示指向数组的第一个(或任何其他)元素的指针,则不需要进行数组到指针衰减,因为要添加i的指针已经存在:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
       +---+---+---+---+---+---+---+---+
       |   |   |   |   |   |   |   |   |   int[8]
       +---+---+---+---+---+---+---+---+
         ^           ^               ^
         |           |               |
         |           |               |
       +-|-+         |               |
    x: | | |    x+3  |          x+7  |     int*
       +---+

    注意,在所描述的情况下,x是一个指针变量(可通过x旁边的小方框识别),但它也可能是返回指针的函数(或T*类型的任何其他表达式)的结果。好的。索引运算符

    由于语法EDOCX1 OR 13是有点笨拙的,C++提供了另一种语法x[i]:好的。

    1
    std::cout << x[3] <<"," << x[7] << std::endl;

    由于加法是可交换的,因此以下代码的作用完全相同:好的。

    1
    std::cout << 3[x] <<"," << 7[x] << std::endl;

    索引运算符的定义导致以下有趣的等价性:好的。

    1
    &x[i]  ==  &*(x+i)  ==  x+i

    但是,&x[0]一般不等同于x。前者是指针,后者是数组。只有当上下文触发数组到指针的衰减时,x&x[0]才能互换使用。例如:好的。

    1
    2
    T* p = &array[0];  // rewritten as &*(array+0), decay happens due to the addition
    T* q = array;      // decay happens due to the assignment

    在第一行,编译器检测到从指针到指针的赋值,这是非常简单的成功。在第二行,它检测到从数组到指针的赋值。由于这是无意义的(但指针到指针的分配是有意义的),数组到指针的衰减像往常一样开始。好的。范围

    T[n]类型的数组有n个元素,从0索引到n-1;没有n个元素。然而,为了支持半开放的范围(其中开始是包容性的,而结束是排他的),C++允许计算指向(不存在)第n个元素的指针,但是去引用该指针是非法的:好的。

    1
    2
    3
    4
    5
    6
    7
    8
       +---+---+---+---+---+---+---+---+....
    x: |   |   |   |   |   |   |   |   |   .   int[8]
       +---+---+---+---+---+---+---+---+....
         ^                               ^
         |                               |
         |                               |
         |                               |
    x+0  |                          x+8  |     int*

    例如,如果要对数组进行排序,以下两种方法都同样有效:好的。

    1
    2
    std::sort(x + 0, x + n);
    std::sort(&x[0], &x[0] + n);

    请注意,将EDCOX1作为0个参数作为第二个参数是非法的,因为这相当于EDCOX1 OR 1,并且子表达式EDCOX1 OR 2在技术上调用C++中的未定义行为(但不在C99中调用)。好的。

    还要注意,您可以简单地提供x作为第一个参数。这对我来说有点太短了,而且它也让编译器对模板参数的推导有点困难,因为在这种情况下,第一个参数是数组,而第二个参数是指针。(同样,数组到指针的衰减开始。)好的。好啊。


    程序员经常混淆多维数组和指针数组。

    多维数组

    大多数程序员都熟悉命名的多维数组,但许多人不知道多维数组也可以匿名创建。多维数组通常被称为"数组"或"真正的多维数组"。

    命名多维数组

    使用命名多维数组时,所有维度都必须在编译时已知:

    1
    2
    3
    4
    5
    6
    7
    8
    int H = read_int();
    int W = read_int();

    int connect_four[6][7];   // okay

    int connect_four[H][7];   // ISO C++ forbids variable length array
    int connect_four[6][W];   // ISO C++ forbids variable length array
    int connect_four[H][W];   // ISO C++ forbids variable length array

    这是命名多维数组在内存中的外观:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
                  +---+---+---+---+---+---+---+
    connect_four: |   |   |   |   |   |   |   |
                  +---+---+---+---+---+---+---+
                  |   |   |   |   |   |   |   |
                  +---+---+---+---+---+---+---+
                  |   |   |   |   |   |   |   |
                  +---+---+---+---+---+---+---+
                  |   |   |   |   |   |   |   |
                  +---+---+---+---+---+---+---+
                  |   |   |   |   |   |   |   |
                  +---+---+---+---+---+---+---+
                  |   |   |   |   |   |   |   |
                  +---+---+---+---+---+---+---+

    请注意,上面这样的二维网格仅仅是有帮助的可视化。从C++的观点来看,内存是字节的"扁平"序列。多维数组的元素按行主顺序存储。也就是说,connect_four[0][6]connect_four[1][0]是内存中的邻居。实际上,connect_four[0][7]connect_four[1][0]表示相同的元素!这意味着您可以获取多维数组并将其视为大型一维数组:

    1
    2
    3
    int* p = &connect_four[0][0];
    int* q = p + 42;
    some_int_sequence_algorithm(p, q);

    匿名多维数组

    对于匿名多维数组,除第一个之外的所有维度都必须在编译时已知:

    1
    2
    3
    4
    5
    int (*p)[7] = new int[6][7];   // okay
    int (*p)[7] = new int[H][7];   // okay

    int (*p)[W] = new int[6][W];   // ISO C++ forbids variable length array
    int (*p)[W] = new int[H][W];   // ISO C++ forbids variable length array

    这就是在内存中匿名多维数组的样子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
                  +---+---+---+---+---+---+---+
            +---> |   |   |   |   |   |   |   |
            |     +---+---+---+---+---+---+---+
            |     |   |   |   |   |   |   |   |
            |     +---+---+---+---+---+---+---+
            |     |   |   |   |   |   |   |   |
            |     +---+---+---+---+---+---+---+
            |     |   |   |   |   |   |   |   |
            |     +---+---+---+---+---+---+---+
            |     |   |   |   |   |   |   |   |
            |     +---+---+---+---+---+---+---+
            |     |   |   |   |   |   |   |   |
            |     +---+---+---+---+---+---+---+
            |
          +-|-+
       p: | | |
          +---+

    请注意,数组本身仍然作为内存中的单个块分配。

    指针数组

    您可以通过引入另一个间接级别来克服固定宽度的限制。

    指针的命名数组

    下面是一个由五个指针组成的命名数组,这些指针由不同长度的匿名数组初始化:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    int* triangle[5];
    for (int i = 0; i < 5; ++i)
    {
        triangle[i] = new int[5 - i];
    }

    // ...

    for (int i = 0; i < 5; ++i)
    {
        delete[] triangle[i];
    }

    这就是它在记忆中的样子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
              +---+---+---+---+---+
              |   |   |   |   |   |
              +---+---+---+---+---+
                ^
                | +---+---+---+---+
                | |   |   |   |   |
                | +---+---+---+---+
                |   ^
                |   | +---+---+---+
                |   | |   |   |   |
                |   | +---+---+---+
                |   |   ^
                |   |   | +---+---+
                |   |   | |   |   |
                |   |   | +---+---+
                |   |   |   ^
                |   |   |   | +---+
                |   |   |   | |   |
                |   |   |   | +---+
                |   |   |   |   ^
                |   |   |   |   |
                |   |   |   |   |
              +-|-+-|-+-|-+-|-+-|-+
    triangle: | | | | | | | | | | |
              +---+---+---+---+---+

    由于现在每一行都是单独分配的,因此将二维数组视为一维数组不再有效。

    指针的匿名数组

    这里是一个由5个(或任何其他数量)指针组成的匿名数组,这些指针由不同长度的匿名数组初始化:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    int n = calculate_five();   // or any other number
    int** p = new int*[n];
    for (int i = 0; i < n; ++i)
    {
        p[i] = new int[n - i];
    }

    // ...

    for (int i = 0; i < n; ++i)
    {
        delete[] p[i];
    }
    delete[] p;   // note the extra delete[] !

    这就是它在记忆中的样子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
              +---+---+---+---+---+
              |   |   |   |   |   |
              +---+---+---+---+---+
                ^
                | +---+---+---+---+
                | |   |   |   |   |
                | +---+---+---+---+
                |   ^
                |   | +---+---+---+
                |   | |   |   |   |
                |   | +---+---+---+
                |   |   ^
                |   |   | +---+---+
                |   |   | |   |   |
                |   |   | +---+---+
                |   |   |   ^
                |   |   |   | +---+
                |   |   |   | |   |
                |   |   |   | +---+
                |   |   |   |   ^
                |   |   |   |   |
                |   |   |   |   |
              +-|-+-|-+-|-+-|-+-|-+
              | | | | | | | | | | |
              +---+---+---+---+---+
                ^
                |
                |
              +-|-+
           p: | | |
              +---+

    转换

    数组到指针的衰减自然扩展到数组和指针数组:

    1
    2
    3
    4
    5
    int array_of_arrays[6][7];
    int (*pointer_to_array)[7] = array_of_arrays;

    int* array_of_pointers[6];
    int** pointer_to_pointer = array_of_pointers;

    但是,没有从T[h][w]T**的隐式转换。如果确实存在这种隐式转换,则结果将是指向h数组的第一个元素的指针指向T数组的第一个元素(每个元素都指向原始二维数组中一行的第一个元素),但该指针数组在内存中的任何位置都不存在。如果需要这样的转换,必须手动创建和填充所需的指针数组:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    int connect_four[6][7];

    int** p = new int*[6];
    for (int i = 0; i < 6; ++i)
    {
        p[i] = connect_four[i];
    }

    // ...

    delete[] p;

    请注意,这将生成原始多维数组的视图。如果您需要一个副本,您必须创建额外的数组并自己复制数据:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    int connect_four[6][7];

    int** p = new int*[6];
    for (int i = 0; i < 6; ++i)
    {
        p[i] = new int[7];
        std::copy(connect_four[i], connect_four[i + 1], p[i]);
    }

    // ...

    for (int i = 0; i < 6; ++i)
    {
        delete[] p[i];
    }
    delete[] p;


    转让

    由于没有特殊原因,数组不能彼此分配。用std::copy代替:

    1
    2
    3
    4
    5
    6
    7
    #include

    // ...

    int a[8] = {2, 3, 5, 7, 11, 13, 17, 19};
    int b[8];
    std::copy(a + 0, a + 8, b);

    这比真正的数组分配提供的更灵活,因为可以将较大数组的切片复制到较小的数组中。std::copy通常专门用于原始类型,以提供最大的性能。std::memcpy的表现不太可能更好。如有疑问,请测量。

    虽然不能直接分配数组,但可以分配包含数组成员的结构和类。这是因为数组成员是由编译器默认提供的赋值运算符以成员方式复制的。如果为自己的结构或类类型手动定义赋值运算符,则必须返回到数组成员的手动复制。

    参数传递

    数组不能按值传递。您可以通过指针或引用传递它们。

    传递指针

    因为数组本身不能按值传递,所以通常指向第一个元素的指针是按值传递的。这通常被称为"传递指针"。由于数组的大小不能通过该指针检索,所以必须传递指示数组大小的第二个参数(经典C解决方案)或指向数组最后元素(C++迭代器解决方案)之后的第二指针:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include <numeric>
    #include <cstddef>

    int sum(const int* p, std::size_t n)
    {
        return std::accumulate(p, p + n, 0);
    }

    int sum(const int* p, const int* q)
    {
        return std::accumulate(p, q, 0);
    }

    作为一种语法选择,您还可以将参数声明为T p[],这意味着仅在参数列表上下文中与T* p完全相同:

    1
    2
    3
    4
    int sum(const int p[], std::size_t n)
    {
        return std::accumulate(p, p + n, 0);
    }

    您可以认为编译器只是在参数列表的上下文中将T p[]重写为T *p。这个特殊的规则在一定程度上导致了数组和指针的混乱。在其他任何上下文中,将某个对象声明为数组或指针都会产生巨大的差异。

    不幸的是,您还可以在数组参数中提供一个大小,编译器会自动忽略该大小。也就是说,以下三个签名完全等效,如编译器错误所示:

    1
    2
    3
    4
    5
    6
    7
    int sum(const int* p, std::size_t n)

    // error: redefinition of 'int sum(const int*, size_t)'
    int sum(const int p[], std::size_t n)

    // error: redefinition of 'int sum(const int*, size_t)'
    int sum(const int p[8], std::size_t n)   // the 8 has no meaning here

    通过引用

    数组也可以通过引用传递:

    1
    2
    3
    4
    int sum(const int (&a)[8])
    {
        return std::accumulate(a + 0, a + 8, 0);
    }

    在这种情况下,数组大小很重要。由于编写一个只接受8个元素数组的函数几乎没有什么用处,程序员通常编写模板这样的函数:

    1
    2
    3
    4
    5
    template <std::size_t n>
    int sum(const int (&a)[n])
    {
        return std::accumulate(a + 0, a + n, 0);
    }

    请注意,只能使用实际的整数数组调用这样的函数模板,而不能使用指向整数的指针。数组的大小是自动推断的,对于每个大小的n,都会从模板中实例化不同的函数。您还可以编写非常有用的函数模板,从元素类型和大小中进行抽象。


    数组创建和初始化

    与任何其他类型的C++对象一样,数组可以直接存储在命名变量中(然后,大小必须是编译时常数;C++不支持VLAs),或者它们可以匿名地存储在堆上,并通过指针间接访问(只有在运行时才能计算大小)。好的。自动阵列

    每次控制流通过非静态局部数组变量定义时,都会创建自动数组(数组位于堆栈上):好的。

    1
    2
    3
    4
    void foo()
    {
        int automatic_array[8];
    }

    初始化按升序执行。注意,初始值取决于元素类型T:好的。

    • 如果T是一个pod(如上述示例中的int),则不会进行初始化。
    • 否则,EDOCX1的默认构造函数将初始化所有元素。
    • 如果T没有提供可访问的默认构造函数,则程序不编译。

    或者,可以在数组初始值设定项中显式指定初始值,数组初始值设定项是一个逗号分隔的列表,由大括号包围:好的。

    1
        int primes[8] = {2, 3, 5, 7, 11, 13, 17, 19};

    由于在这种情况下,数组初始值设定项中的元素数等于数组的大小,因此手动指定大小是多余的。它可以由编译器自动推断:好的。

    1
        int primes[] = {2, 3, 5, 7, 11, 13, 17, 19};   // size 8 is deduced

    也可以指定大小并提供较短的数组初始值设定项:好的。

    1
        int fibonacci[50] = {0, 1, 1};   // 47 trailing zeros are deduced

    在这种情况下,其余的元素被初始化为零。请注意,C++允许空数组初始化器(所有元素都为零初始化),而C89不(至少需要一个值)。还要注意,数组初始值设定项只能用于初始化数组;以后不能在赋值中使用它们。好的。静态数组

    静态数组(位于"数据段"中的数组)是使用static关键字定义的局部数组变量,以及命名空间范围内的数组变量("全局变量"):好的。

    1
    2
    3
    4
    5
    6
    int global_static_array[8];

    void foo()
    {
        static int local_static_array[8];
    }

    (请注意,命名空间范围内的变量是隐式静态的。将static关键字添加到它们的定义中,其含义完全不同,已弃用。)好的。

    下面是静态数组与自动数组的行为不同之处:好的。

    • 没有数组初始值设定项的静态数组在任何进一步的潜在初始化之前都是零初始化的。
    • 静态pod数组初始化一次,初始值通常烘焙到可执行文件中,在这种情况下,在运行时不需要初始化成本。然而,这并不总是最节省空间的解决方案,而且标准并不要求这样做。
    • 静态非pod数组在控制流第一次通过其定义时初始化。对于局部静态数组,如果从未调用函数,则可能永远不会发生这种情况。

    (以上都不是特定于数组的。这些规则同样适用于其他类型的静态对象。)好的。阵列数据成员

    are created when their members数组数据对象owning is created。不幸的是他/她,C++ 03提供不到固体聚合物arrays均值初始化初始化列表,我initialization must be with assignments:faked>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Foo
    {
        int primes[8];

    public:

        Foo()
        {
            primes[0] = 2;
            primes[1] = 3;
            primes[2] = 5;
            // ...
        }
    };

    你可以定义alternatively,安自动拷贝构造函数体阵列在茶叶和茶元过:>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Foo
    {
        int primes[8];

    public:

        Foo()
        {
            int local_array[] = {2, 3, 5, 7, 11, 13, 17, 19};
            std::copy(local_array + 0, local_array + 8, primes + 0);
        }
    };

    在C++0x中,arrays can be the成员初始化列表initialized thanks to initialization一致:>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Foo
    {
        int primes[8];

    public:

        Foo() : primes { 2, 3, 5, 7, 11, 13, 17, 19 }
        {
        }
    };

    this is the only solution types that have with元,工厂没有默认构造函数。>

    动态arrays

    因此,有没有动态arrays names of the only均值,accessing them is分离开。因为他们都没有名字,他们会推荐为"匿名arrays"从现在。>

    在C,arrays are created malloc匿名朋友的途径。在C + +,arrays are created using the匿名表指针返回到new T[size]which to the first of an匿名数组元素:>

    1
    2
    std::size_t size = compute_size_at_runtime();
    int* p = new int[size];

    ASCII艺术depicts the following the memory if the size is as布局8在计算机运行时:>

    1
    2
    3
    4
    5
    6
    7
    8
    9
                 +---+---+---+---+---+---+---+---+
    (anonymous)  |   |   |   |   |   |   |   |   |
                 +---+---+---+---+---+---+---+---+
                   ^
                   |
                   |
                 +-|-+
              p: | | |                               int*
                 +---+

    明显的,匿名的要求更多的内存比arrays arrays命名两个that must be to the separately存储额外的指针。(there is also some on the additional量免税商店)。>

    注that there is不衰减了指针阵列-在线这里。尽管事实上建立安评教new int[size]does the result of integers阵列,new int[size]of the expression is already在指针(the first to a single integer,not an(元)integers指针阵列或阵列integers of unknown to an of size。这将是不可能的,因为在静电型阵列系统维大小.……to be的编译时间常数。(did not the因此,匿名型阵列与静电记录信息在the picture)。>

    关于元素的默认值为,arrays similar to自动arrays匿名的举止。normally are not initialized匿名arrays,POD,but that there is a special initialization触发器表值:>

    1
    int* p = new int[some_computed_size]();

    (注trailing pair of the before the right semicolon性括号。)再次,C++1x simplifies the rules and initial values for allows specifying arrays thanks to一致initialization:匿名>

    1
    int* p = new int[8] { 2, 3, 5, 7, 11, 13, 17, 19 };

    如果你是做安阵列使用匿名,You have to back to the en:缓释系统>

    1
    delete[] p;

    你必须释放每盎司不确切的说因为是匿名的阵列,然后又afterwards触摸。结果:在所有不释放在存储漏(或更多generally depending on the元,资源型,漏),和试图释放在恩多时报Undefined行为的结果。using the形态(或非阵列deletefreeinstead of)delete[]is also to Undefined数组的释放行为。>

    好吧。


    5。使用数组时常见的陷阱。5.1陷阱:信任型不安全链接。

    好吧,您已经被告知或发现了全局(命名空间可在翻译单元之外访问的范围变量邪恶与贸易;但你知道他们是多么邪恶吗?考虑一下下面的程序,由两个文件[main.cpp]和[numbers.cpp]组成:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // [main.cpp]
    #include <iostream>

    extern int* numbers;

    int main()
    {
        using namespace std;
        for( int i = 0;  i < 42;  ++i )
        {
            cout << (i > 0?"," :"") << numbers[i];
        }
        cout << endl;
    }

    好的。

    1
    2
    // [numbers.cpp]
    int numbers[42] = {1, 2, 3, 4, 5, 6, 7, 8, 9};

    在Windows7中,这与mingw g++4.4.1和Visual C++ 10。好的。

    因为类型不匹配,所以运行程序时程序会崩溃。好的。

    The Windows 7 crash dialog好的。

    在正式解释中:程序有未定义的行为(ub),而不是所以它可以挂起来,或者什么也不做,或者它可以给美国、俄罗斯、印度总统发三封电子邮件,中国和瑞士,让鼻腔守护者飞出你的鼻子。好的。

    在实践中说明:在main.cpp中,数组被视为指针,放置与数组的地址相同。对于32位可执行文件,这意味着数组中的int值被视为指针。也就是说,在main.cpp中,numbers变量包含或似乎包含(int*)1。这导致了在地址空间的最底部访问内存的程序,即传统的保留和陷阱造成。结果:你撞车了。好的。

    编译器完全有权不诊断此错误,因为C++ 11×3.5/10说明了兼容类型的要求。对于声明,好的。

    [N3290 §3.5/10]
    A violation of this rule on type identity does not require a diagnostic.

    Ok.

    同一段详细说明了允许的变更:好的。

    … declarations for an array object can specify array types that
    differ by the presence or absence of a major array bound (8.3.4).

    Ok.

    允许的变体不包括将名称声明为一个数组翻译单元,作为另一个翻译单元的指针。好的。5.2陷阱:过早优化(memset和朋友)。

    还没有写完好的。5.3陷阱:使用C习语获取元素数量。

    有了丰富的C经验,书写&hellip;是很自然的事。好的。

    1
    #define N_ITEMS( array )   (sizeof( array )/sizeof( array[0] ))

    由于array在需要时衰减到指向第一个元素的指针,因此表达式sizeof(a)/sizeof(a[0])也可以写成sizeof(a)/sizeof(*a)。它的含义相同,无论它如何它是用来查找数组中的数字元素的C语言。好的。

    主要缺陷:C习语不是类型安全的。例如,代码和Helip;好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #include <stdio.h>

    #define N_ITEMS( array ) (sizeof( array )/sizeof( *array ))

    void display( int const a[7] )
    {
        int const   n = N_ITEMS( a );          // Oops.
        printf("%d elements.
    "
    , n );
    }

    int main()
    {
        int const   moohaha[]   = {1, 2, 3, 4, 5, 6, 7};

        printf("%d elements, calling display...
    "
    , N_ITEMS( moohaha ) );
        display( moohaha );
    }

    将指针传递给N_ITEMS,因此很可能会产生错误的结果。在Windows 7中编译为32位可执行文件,生成&hellip;好的。< Buff行情>

    7个元素,调用显示…1要素。好的。< /块引用>

  • 编译器将int const a[7]重写为仅int const a[]
  • 编译器将int const a[]重写为int const* a
  • 因此,使用指针调用N_ITEMS
  • 对于32位可执行文件,EDOCX1(指针大小)是4。
  • sizeof(*array)相当于sizeof(int),对于32位可执行文件,该值也为4。
  • 为了在运行时检测此错误,您可以执行&hellip;好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include
    #include <typeinfo>

    #define N_ITEMS( array )       (                               \
        assert((                                                    \
           "N_ITEMS requires an actual array as argument",        \
            typeid( array ) != typeid( &*array )                    \
            )),                                                     \
        sizeof( array )/sizeof( *array )                            \
        )

    7 elements, calling display...
    Assertion failed: ("N_ITEMS requires an actual array as argument", typeid( a ) != typeid( &*a ) ), file runtime_detect
    ion.cpp, line 16

    Ok.

    This application has requested the Runtime to terminate it in an unusual way.
    Please contact the application's support team for more information.

    Ok.

    运行时错误检测比不检测好,但有点浪费处理器时间,也许还有更多的程序员时间。更好的检测编译时间!如果您不支持用C++ 98支持本地类型的数组,然后你可以这样做:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    #include <stddef.h>

    typedef ptrdiff_t   Size;

    template< class Type, Size n >
    Size n_items( Type (&)[n] ) { return n; }

    #define N_ITEMS( array )       n_items( array )

    用g++把这个定义编译成第一个完整的程序,我得到了&好的。

    M:\count> g++ compile_time_detection.cpp
    compile_time_detection.cpp: In function 'void display(const int*)':
    compile_time_detection.cpp:14: error: no matching function for call to 'n_items(const int*&)'

    Ok.

    M:\count> _

    Ok.

    工作原理:数组是通过引用N_ITEMS传递的,所以它是这样做的。不是衰减到指向第一个元素的指针,函数只返回类型指定的元素数。好的。

    用C++ 11,您也可以使用本地类型的数组,它是类型安全的。用于查找数组元素数的C++习语。好的。5.4 C++ 11和C++ 14陷阱:使用EDCOX1×9数组大小函数。

    用C++ 11,以后它是自然的,但是你会看到危险的!,为了替换C++ 03函数好的。

    1
    2
    3
    4
    typedef ptrdiff_t   Size;

    template< class Type, Size n >
    Size n_items( Type (&)[n] ) { return n; }

    具有好的。

    1
    2
    3
    4
    using Size = ptrdiff_t;

    template< class Type, Size n >
    constexpr auto n_items( Type (&)[n] ) -> Size { return n; }

    其中重大变化是使用constexpr,允许此函数用于生成编译时常量。好的。

    例如,与C++ 03函数相反,这样的编译时间常数可用于声明与另一个数组大小相同的数组:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    // Example 1
    void foo()
    {
        int const x[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 4};
        constexpr Size n = n_items( x );
        int y[n] = {};
        // Using y here.
    }

    但是考虑使用constexpr版本的代码:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // Example 2
    template< class Collection >
    void foo( Collection const& c )
    {
        constexpr int n = n_items( c );     // Not in C++14!
        // Use c here
    }

    auto main() -> int
    {
        int x[42];
        foo( x );
    }

    陷阱:截至2015年7月,上述内容与mingw-64 5.1.0进行了比较。-pedantic-errors,和,在gcc.godbolt.org/和clang 3.0上使用在线编译器进行测试和clang 3.2,但不包括clang 3.3、3.4.1、3.5.0、3.5.1、3.6(rc1)或3.7(实验)。对于Windows平台来说很重要,它不编译用Visual C++ 2015。原因是一个C++ 11/C++ 14关于使用的语句constexpr表达式中的引用:好的。C++ 11 C++ 14 $5.19/2第19条破折号

    A conditional-expression e is a core constant expression unless the evaluation
    of e, following the rules of the abstract machine (1.9), would evaluate one of the
    following expressions:
            ?

    Ok.

    • an id-expression that refers to a variable or data member of reference type
      unless the reference has a preceding initialization and either

      • it is initialized with a constant expression or
      • it is a non-static data member of an object whose lifetime began within
        the evaluation of e;

    一个人总是可以写得更详细好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // Example 3  --  limited

    using Size = ptrdiff_t;

    template< class Collection >
    void foo( Collection const& c )
    {
        constexpr Size n = std::extent< decltype( c ) >::value;
        // Use c here
    }

    &hellip;但当Collection不是原始数组时,此操作失败。好的。

    要处理可以是非数组的集合,需要n_items函数,但对于编译时使用,还需要编译时数组大小的表示。经典C++ 03解决方案,效果不错同样在C++ 11和C++ 14中,是让函数报告其结果不是一个值。但通过它的函数结果类型。例如:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    // Example 4 - OK (not ideal, but portable and safe)

    #include
    #include <stddef.h>

    using Size = ptrdiff_t;

    template< Size n >
    struct Size_carrier
    {
        char sizer[n];
    };

    template< class Type, Size n >
    auto static_n_items( Type (&)[n] )
        -> Size_carrier<n>;
    // No implementation, is used only at compile time.

    template< class Type, size_t n >        // size_t for g++
    auto static_n_items( std::array<Type, n> const& )
        -> Size_carrier<n>;
    // No implementation, is used only at compile time.

    #define STATIC_N_ITEMS( c ) \
        static_cast<Size>( sizeof( static_n_items( c ).sizer ) )


    template< class Collection >
    void foo( Collection const& c )
    {
        constexpr Size n = STATIC_N_ITEMS( c );
        // Use c here
        (void) c;
    }

    auto main() -> int
    {
        int x[42];
        std::array<int, 43> y;
        foo( x );
        foo( y );
    }

    关于static_n_items返回类型的选择:此代码不使用std::integral_constant。因为用std::integral_constant表示结果直接作为constexpr值,重新引入原来的问题。相反对于Size_carrier类,可以让函数直接返回对数组的引用。然而,并不是每个人都熟悉这种语法。好的。

    关于命名:constexpr解决方案的一部分-由于引用而无效问题是要明确选择编译时间常数。好的。

    希望你的问题能得到解决。C++ 17,但在此之前,像EDCOX1(2)以上的宏产生可移植性,例如,对于CLAN和Visual C++编译器,保留类型安全。好的。

    相关:宏不考虑作用域,因此为了避免名称冲突,它可以是最好使用名称前缀,例如MYLIB_STATIC_N_ITEMS。好的。好啊。