C + + 继承位置使用它们几乎无处不在的 C 数组。C + + 提供的是易于使用,较少、 易出错的抽象 (std::vector<T>因为 C + + 98 和std::array<T, n> C + + 11起),因此需要数组不会发生相当频繁地在 c。但是,当您阅读旧代码或使用用 c 语言编写的库进行交互,应具有牢固掌握数组是如何工作的。

本常见问题解答分为五个部分︰

  1. 在类型级别和访问元素的数组
  2. 数组创建和初始化
  3. 工作分配和传递的参数
  4. 多维数组和指针数组
  5. 使用数组时的常见错误

如果您认为此常见问题解答中缺少了什么重要的东西,写答案,此处链接作为一个附加的组成部分。

下面的文本,在"阵列"意味着"C 数组",没有类模板std::array假设 C 声明符语法的基本知识。请注意,下面的new的和delete手动使用演示是处理异常情况,非常危险,但这是另一个常见问题的主题.

(注意︰ 这应进入堆栈溢出的 c + + 的常见问题解答如果您想要 critique 然后提供此窗体中的常见问题的主意上启动所有的元过帐这应该到这里来做到这一点。这个问题的答案进行监控,在c + + chatroom,FAQ 想法起始位置出的初衷,因此您的答案是很容易获得阅读的人提出了这一想法。)

2011-01-26 22:14:35
问题评论:

他们会更好一些,如果指针总是指向的开始而不是在某处中间其目标也是如此...

因为它提供了更大的灵活性,您应使用 STL 向量。

回答:

在类型级别上的阵列

数组类型可表示为T[n]其中T元素类型n是正大小,该数组中的元素数。数组类型是产品类型的元素类型和大小。如果一个或两个这些成分不同,将会得到不同的类型︰

#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],而编译器将自动提供该指针︰

                  +---+---+---+---+---+---+---+---+
the_actual_array: |   |   |   |   |   |   |   |   |   int[8]
                  +---+---+---+---+---+---+---+---+
                    ^
                    |
                    |
                    |
                    |  pointer_to_the_first_element   int*

"数组的指针衰减",就是这种转换,很混乱的一个主要来源。因为它不再是类型 (T*) 的一部分,在此过程中,丢失数组的大小。专业︰ 忘记在类型级别上的阵列的大小允许指向任何大小的数组的第一个元素的指针。骗子︰ 给定一个指向数组的第一个 (或任何其他) 元素,没有方法来检测该数组的大小或位置将指针准确地指向相对于数组的界限。指针是非常愚蠢.

数组不是指针

只要认为有用,也就是说,只要操作者在一个阵列上失败,但在指针上成功,则编译器将自动生成数组的第一个元素的指针。由于结果指针是只是该数组的地址,是微不足道的此数组转换为指针。请注意,指针本身 (或在内存中的其他任何地方)存储为数组的一部分。数组不是指针。

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

一个重要的上下文中的数组 does到为其第一个元素的指针的衰变时&运算符应用于它。在这种情况下, &运算符会产生整个阵列,而不仅仅是对其第一个元素的指针的指针。在这种情况下的值(地址) 是相同的尽管数组的第一个元素的指针和整个数组的指针是完全不同的类型︰

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

下面的 ascii 字符图说明了这种区别︰

      +-----------------------------------+
      | +---+---+---+---+---+---+---+---+ |
+---> | |   |   |   |   |   |   |   |   | | 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 个元素。随后取消引用的指针,一个可以访问单个元素︰

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

如果x表示一个数组,然后数组的指针衰变的启动中,因为添加一个数组和一个整数是没有意义的 (没有阵列上的加上操作),但添加一个指针和整数有意义︰

   +---+---+---+---+---+---+---+---+
x: |   |   |   |   |   |   |   |   |   int[8]
   +---+---+---+---+---+---+---+---+
     ^           ^               ^
     |           |               |
     |           |               |
     |           |               |
x+0  |      x+3  |          x+7  |     int*

(请注意,生成隐式指针没有名称,因此,为了确定其编写x+0 。)

如果在另一方面, x表示数组的第一个 (或任何其他) 元素指向的指针,然后数组的指针衰减不必要,因为i打算已经添加的指针存在︰

   +---+---+---+---+---+---+---+---+
   |   |   |   |   |   |   |   |   |   int[8]
   +---+---+---+---+---+---+---+---+
     ^           ^               ^
     |           |               |
     |           |               |
   +-|-+         |               |
x: | | |    x+3  |          x+7  |     int*
   +---+

请注意,所显示的情况下, x是一个指针变量(以小框旁边x的标识),但它可能也很好的一个函数,该函数返回一个指针 (或任何其他表达式类型T*的结果).

索引运算符

C + + 语法*(x+i)是有点不太妥当,因为提供的替代语法x[i]:

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

由于这样的事实,就是律,以下代码可执行完全相同︰

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

索引运算符的定义产生了以下有趣的相等性︰

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

然而,&x[0]通常是等于x前者是后者的一个数组的指针。只有在上下文触发数组的指针衰变时x&x[0]可互换。例如︰

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个元素,索引为0n-1;没有元素n并且还,以支持半开放范围 (开始属于价内税,最终是独占),c + + 允许计算 (不存在) 第 n 个元素,指向的指针,但它是非法取消引用的指针︰

   +---+---+---+---+---+---+---+---+....
x: |   |   |   |   |   |   |   |   |   .   int[8]
   +---+---+---+---+---+---+---+---+....
     ^                               ^
     |                               |
     |                               |
     |                               |
x+0  |                          x+8  |     int*

例如,如果您想要对数组进行排序,以下两个会都很有效︰

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

请注意,是非法的因为这相当于&*(x+n),和子表达式*(x+n)技术上调用未定义的行为在 c + + (但不是在 C99) 作为第二个参数提供&x[n]

此外请注意您可能只需提供x作为第一个参数。这就是对我的口味,有点太简洁,它还可以使模板参数推断有点困难的编译器中,因为在这种情况下,第一个参数是一个数组,而第二个参数是一个指针。(同样,数组的指针衰变改观。)

情况下,数组不衰变到指针是供参考的处所

程序员往往混淆了指针数组与多维数组。

多维数组

大多数程序员都熟悉名为多维数组,但是许多都没有意识到这一事实也可以以匿名方式创建多维数组。多维数组通常称为"数组的数组"或"为多维数组"。

名为多维数组

当使用名为多维数组时,则必须在编译时已知的所有尺寸︰

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

这是如何命名的多维数组类似内存中︰

              +---+---+---+---+---+---+---+
connect_four: |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+

请注意,如上述的 2D 网格只是有用的可视化效果。从 c + + 的角度来说,内存是"平面"的一个字节序列。多维数组的元素按行优先的顺序存储。即, connect_four[0][6]connect_four[1][0]是在内存中的邻居。事实上, connect_four[0][7]connect_four[1][0]表示同一元素 !这意味着您可以采用多维数组并将它们视为大量的一维数组︰

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

匿名的多维数组

对于匿名的多维数组,所有尺寸除第一个必须是在编译时都已知︰

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

这是如何匿名的多维数组类似内存中︰

              +---+---+---+---+---+---+---+
        +---> |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |
      +-|-+
   p: | | |
      +---+

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

指针数组

通过引入另一级别的间接寻址,可以克服固定宽度的限制。

指定的数组的指针

下面是五个被初始化的指针匿名数组的长度不同的命名的数组︰

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];
}

而以下是如何看起来像在内存中︰

          +---+---+---+---+---+
          |   |   |   |   |   |
          +---+---+---+---+---+
            ^
            | +---+---+---+---+
            | |   |   |   |   |
            | +---+---+---+---+
            |   ^
            |   | +---+---+---+
            |   | |   |   |   |
            |   | +---+---+---+
            |   |   ^
            |   |   | +---+---+
            |   |   | |   |   |
            |   |   | +---+---+
            |   |   |   ^
            |   |   |   | +---+
            |   |   |   | |   |
            |   |   |   | +---+
            |   |   |   |   ^
            |   |   |   |   |
            |   |   |   |   |
          +-|-+-|-+-|-+-|-+-|-+
triangle: | | | | | | | | | | |
          +---+---+---+---+---+

因为每一行分配在现在分别,查看二维数组作为一维数组不工作了。

匿名数组的指针

这里是 5 一个匿名数组 (或任意数量的) 用不同长度的匿名数组初始化的指针︰

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[] !

而以下是如何看起来像在内存中︰

          +---+---+---+---+---+
          |   |   |   |   |   |
          +---+---+---+---+---+
            ^
            | +---+---+---+---+
            | |   |   |   |   |
            | +---+---+---+---+
            |   ^
            |   | +---+---+---+
            |   | |   |   |   |
            |   | +---+---+---+
            |   |   ^
            |   |   | +---+---+
            |   |   | |   |   |
            |   |   | +---+---+
            |   |   |   ^
            |   |   |   | +---+
            |   |   |   | |   |
            |   |   |   | +---+
            |   |   |   |   ^
            |   |   |   |   |
            |   |   |   |   |
          +-|-+-|-+-|-+-|-+-|-+
          | | | | | | | | | | |
          +---+---+---+---+---+
            ^
            |
            |
          +-|-+
       p: | | |
          +---+

转换

数组的指针衰减很自然地扩展到数组和指针数组的数组中︰

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** T[h][w]从隐式转换。如果确实存在这样的隐式转换,结果是T (每个指向第一个元素的原始的二维数组中的行),到h指针数组的第一个元素的指针,但指针数组尚不存在任何地方在内存中。如果需要这种转换,您必须创建并手动填充所需的指针数组︰

int connect_four[6][7];

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

// ...

delete[] p;

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

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 :

#include <algorithm>

// ...

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的性能会更好。如果有疑问,测量。

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

参数传递

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

通过指针传递

由于阵列本身不能通过值传递,通常为他们的第一个元素的指针均通过值传递相反。这通常称为"通过指针传递"。由于无法检索,则可以通过该指针数组的大小,您必须传递第二个参数指示数组 (经典的 C 解决方案) 或指向最后一个元素的数组 (c + + 的迭代器解决方案) 之后的第二个指针的大小︰

#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 参数的上下文中列出了只:

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

您可以重写T p[]作为编译器认为T *p 的上下文参数中列出了只此特殊规则是部分负责有关数组和指针的整个混乱。在其他所有上下文,作为数组或指针声明内容差别巨大

遗憾的是,您还可以提供将以静默方式由编译器忽略该数组参数中的大小。即,以下三个特征码是完全等效的由编译器错误︰

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

通过引用传递

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

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

在这种情况下,数组大小非常重要。编写函数只接受正好是 8 的元素的数组是几乎没有什么用处,因为程序员通常编写此类函数作为模板︰

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

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

数组创建和初始化

数组可以存储与其他类型的 c + + 对象,或者直接在命名变量 (然后大小必须是一个编译时常量;C + + 中不支持 VLAs),或者可以以匿名方式存储在堆上并通过指针进行间接访问 (只有这样可以计算出大小在运行时)。

自动数组

控制流通过定义非静态的局部数组变量的每次创建自动数组 (数组生活"堆栈"上)︰

void foo()
{
    int automatic_array[8];
}

以升序顺序执行初始化。请注意的初始值依赖于元素类型T:

  • 如果T(如上面的示例中为int ),没有初始化将发生。
  • 否则, T -默认构造函数初始化的所有元素。
  • 如果T提供任何可访问的默认构造函数,则该程序将不编译。

或者,可以在数组初始值设定项,以逗号分隔的列表括在大括号中显式指定的初始值︰

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

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

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

还有可能指定的大小,并提供更短的数组初始值设定项︰

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

在这种情况下,剩余的元素初始化为零。请注意,c + + 允许空数组初始值设定项 (所有元素都都初始化为零的),而 C89 不 (至少一个值是必需的)。此外应注意,数组初始值设定项仅用于初始化数组;他们以后不能在分配中使用。

静态数组

静态数组 (数组生活"中的数据段") 是在命名空间范围 ("全局变量") 的static关键字和数组变量定义的局部数组变量︰

int global_static_array[8];

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

(注意︰ 在命名空间范围内的变量是静态隐式。static关键字添加到其定义具有完全不同的、 过时的含义.)

下面是如何静态数组的行为自动数组与不同︰

  • 而一个数组初始值设定项的静态数组初始化为零之前进一步潜在的任何初始化。
  • 静态的 POD 数组是初始化一次,并且初始值通常融入该可执行文件,在这种情况下有是在运行时初始化免费。但是,这并不总是空间最有效的解决方案,并且标准不需要。
  • 静态非盒数组进行初始化第一次控制流通过它们的定义。在本地的静态数组的情况下,可能永远无法实现如果永远不会调用该函数。

(以上均不是特定于阵列。这些规则应用同样也为其他类型的静态对象)。

阵列数据成员

其所属的对象在创建时创建数组的数据成员。遗憾的是,C + + 03 提供了没有方法来初始化阵列成员初始值设定项列表中,因此必须与分配伪造的初始化︰

class Foo
{
    int primes[8];

public:

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

或者,可以定义自动数组构造函数体中,并将元素复制到︰

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,数组可以:

class Foo
{
    int primes[8];

public:

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

这是唯一的解决方案,适用于有没有默认构造函数的元素类型。

动态数组

动态数组没有名称,因此访问它们的唯一方法是通过指针。因为它们有没有名称,我将它们作为引用"匿名数组"从现在起。

在 C 中,匿名数组创建通过malloc和朋友。在 c + + 中,使用它返回一个匿名数组的第一个元素的指针的new T[size]语法创建匿名数组︰

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

如果为 8 在运行时计算大小,以下 ascii 字符图描述了内存布局︰

             +---+---+---+---+---+---+---+---+
(anonymous)  |   |   |   |   |   |   |   |   |
             +---+---+---+---+---+---+---+---+
               ^
               |
               |
             +-|-+
          p: | | |                               int*
             +---+

显然,匿名数组需要更多的内存,比命名由于额外必须分开存储的指针的数组。(还有一些额外的开销上可用的存储。)

请注意,任何数组的指针衰变呢。尽管评估new int[size]实际上并不创建数组的整数,表达式new int[size]的结果是已经为一个整数 (第一个元素),的整数或未知大小的整数数组的指针数组的指针。这将是不可能的因为静态类型系统需要数组大小为编译时常量。(因此,我不加注释与图片中的静态类型信息的匿名数组。)

有关元素的默认值,匿名数组的行为类似于自动数组。通常情况下,未初始化匿名盒阵列,但有一种特殊的语法,触发值初始化︰

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

(请注意在分号之前的右括号的尾随一对)。再次,C + + 0x 简化规则,并允许指定匿名由于统一初始化数组的初始值︰

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

如果您已完成使用一个匿名数组,则必须将其释放回系统︰

delete[] p;

必须一次性释放每个匿名数组,然后永远不会再次以后接触。不释放它在所有结果中存在内存泄漏 (或更一般的情况下,取决于元素类型,资源短缺),并尝试多次将其释放会导致未定义的行为。使用非数组形式delete(或free) 而不是delete[]以释放该数组也是未定义的行为.

在 C + + 11 中移除了否决的命名空间范围内的static使用。

new则是运算符,因为它肯定无法返回 allcated 数组通过引用。没有只是点到它...

@Deduplicator 没有 it 可能不是这样,因为从历史上看,new远早于引用。

@FredOverflow︰ 所以它不能返回一个引用的原因,是刚才完全不同于书面解释。

我并不认为对未知的界限的数组的引用 @Deduplicator 存在。至少 g + + 拒绝编译int a[10]; int (&r)[] = a;

5.常见的错误时使用数组。

5.1 缺陷︰ 信任类型不安全的链接。

好了,您已经被告知,或已经找到自己,全局 (命名空间范围变量是可翻译单位以外) 是 Evil™。但您知道如何真正 Evil™ 是吗?请考虑下面,两个文件 [main.cpp] 和 [numbers.cpp] 组成的程序︰

// [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;
}

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

在 Windows 7 中,这样编译并链接正常 MinGW g + + 4.4.1 和 Visual C++ 10.0。

由于类型不匹配,当您运行该程序将崩溃。

The Windows 7 crash dialog

正式的解释︰ 该程序具有未定义问题 (副) 并且可以因此不仅仅是挂起,而不是崩溃或可能不执行任何操作,或可以将 threating 的电子邮件发送给美国、 俄罗斯、 印度、 中国和瑞士,劳工并使 Nasal 守护程序从您的鼻子飞。

练习说明︰ 在main.cpp数组被视为放置在相同的地址数组的指针。对于 32 位可执行文件,这意味着在数组中的第一个int值被视为一个指针。即在main.cppnumbers变量包含,或似乎包含, (int*)1这会导致程序访问下在最底部的地址空间,也就是通常保留和陷阱导致的内存。结果︰ 获得了崩溃。

编译器,这是完全不诊断此错误,请其权限内因为 C + + 11 3.5/10 说,有关的兼容类型的声明,要求

[N3290 3.5 / 10]
此规则类型标识上的冲突不需要诊断程序。

在同一段落详细介绍了所允许的变体︰

...数组对象的声明可以指定数组类型具有不同的主要阵列绑定 (8.3.4) 是否存在。

此允许的变化不包括声明一个名为一个数组,用一个翻译单元,和另一个翻译单元的指针。

5.2 缺陷︰ 做过早的优化 (memset和朋友)。

还没有写好

5.3 缺陷︰ 使用 C 方法获取数字的元素。

与深层 C 体验它的自然写...

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

因为在需要表达式sizeof(a)/sizeof(a[0])还可以写为sizeof(a)/sizeof(*a)的第一个元素指向指针的指针arraydecays。其意义都是相同的而且不论如何编写C 用法用于查找数组中的元素数。

主要的缺陷︰ C 用法不是类型安全。例如,代码...

#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,并因此最有可能产生错误的结果。编译为 32 位可执行文件在 Windows 7 中它产生...

7 个元素,调用显示...
1 的元素。

  1. 编译器重写int const a[7]int const a[].
  2. 编译器重写int const a[]int const* a.
  3. 使用一个指针,因此调用N_ITEMS
  4. 对于 32 位可执行文件sizeof(array) (指针的大小),就 4。
  5. sizeof(*array)是等同于sizeof(int),其中 32 位可执行文件也是 4。

若要在运行时检测到此错误,您可以执行...

#include <assert.h>
#include <typeinfo>

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

7 个元素,调用显示...
断言失败: ("N_ITEMS 要求实际数组作为参数",(a) 类型 id ! = 来说 (& *)),文件 runtime_detect ion.cpp,第 16 行

此应用程序要求运行时终止它在一种不寻常的方法。
请有关详细信息,与应用程序的支持小组联系。

运行时错误检测优于未检测到,但它会浪费一些处理器时间,并可能是很多更多的程序员时间。在编译时检测更好 !并且,如果你乐意支持 C + + 98 局部类型的数组,然后您可以这样做︰

#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︰ 在函数显示空洞 (const int *):
compile_time_detection.cpp:14︰ 错误︰ 不匹配的调用的函数 n_items (const int * &)

M:count > _

它的工作原理︰ 数组通过引用传递到n_items,因此它不会不衰变到指针,它指向第一个元素,和函数可以只返回指定类型的元素的数目。

与 C + + 11 您还可以使用此本地类型,数组的它是类型安全c + + 方法用于查找数组中的元素的数目。

5.4 c + + 11 和 C + + 14 缺陷︰ 使用constexpr数组大小的函数。

与 C + + 11 及更高版本它是自然的但当您将看到危险 ! 来替换 C + + 03 函数

typedef ptrdiff_t   Size;

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

使用

using Size = ptrdiff_t;

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

其中明显的变化是使用constexpr,它允许该函数以生成编译时常数.

例如,与 C + + 03 函数,这样一个编译时常量可用于将另一个相同大小的数组声明︰

// 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版本此代码︰

// 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 );
}

缺陷︰ 截至 7 月 2015年以上编译与 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 9短划线

条件表达式 e核心常量表达式,除非e,下面的抽象机 (1.9) 规则的评估将评估下列两个表达式之一︰

  • 除非引用了上述的初始化,并指引用类型的变量或数据成员id 表达式
    • 使用常数表达式初始化或
    • 它是电子的一个对象,该对象的生存期内; 评估开始的非静态数据成员

一个始终可以编写更详细

// 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
}

...但是这失败时Collection不是一个原始数组。

可以为非数组的集合处理一个需要一个n_items函数,overloadability 但还,用于编译时使用一个需要数组大小的编译时表示。和经典 C + + 03 解决方案,它可以正常工作还在 C + + 11 和 14 C + + 中,是使其结果不是一个值,但其功能通过结果类型的函数报告。例如像下面这样︰

// Example 4 - OK (not ideal, but portable and safe)

#include <array>
#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 ,因为作为一个constexpr值,重新引入原始问题直接与std::integral_constant表示的结果。而不是Size_carrier类,一个可以让函数直接返回到一个数组的引用。但是,不是每个人都是熟悉的语法。

关于命名︰ constexpr对此解决方案的一部分-无效-到期-到-引用的问题是要明确编译时常数的选择。

但愿 oops-there-was-a-reference-involved-in-your-constexpr问题将得到解决 C + + 17,但到那时像STATIC_N_ITEMS之上产生的可移植性,如在与 clang Visual C++ 编译器保留类型安全的宏。

相关︰ 宏不受作用域,这样可避免名称冲突,它可以是最好使用一个名称的前缀,例如MYLIB_STATIC_N_ITEMS.

+ 编写测试代码的好 C 1︰ 我有花 15 分钟时间,在 VC + + 10.0 和 GCC 4.1.2 尝试修复该Segmentation fault...我最后找到/理解阅读您的解释后 !请编写第 5.2 节部分:-)Cheers

很好。一个特别说明-countOf 的返回类型应 size_t 而不是 ptrdiff_t。它可能是值得一提的是,在 C + + 11/14 中它应该 constexpr 和 noexcept。

@Ricky65︰ 感谢您的一提的是 C + + 11 注意事项。对这些功能的支持已即将为 Visual C++ 中晚期。关于size_t,有没有优势,我知道的现代的平台,但它有大量的由于 C 和 c + + 的隐式类型转换规则的问题。也就是说, ptrdiff_t是非常有意,用来避免size_t的问题。但是应该注意,g + + 具有匹配模板参数的数组大小,除非它是size_t问题 (我并不认为非此特定于编译器的问题-size_t很重要,但 YMMV)。

@Alf。该标准的工作草案 (N3936) 在我阅读 8.3.4-数组的界限是..."转换常数表达式的类型 std::size_t 和它的值应大于零"。

@Ricky︰ 如果您要引用不一致的问题,此语句中不存在当前 C + + 11 标准以便很难猜出上下文,但矛盾 (动态分配的数组,可以是绑定 0,每个 C + + 11 §5.3.4 / 7) 可能不会结束 14 C + + 中。草稿是指︰ 草稿。如果您改为在问什么"其"引用,它是指原始的表达式中,不被转换的一个。如果您认为或许这类句子意味着人应该使用size_t表示的数组的大小,不当然不是,因为说这第三个现货。

内容来源于Stack Overflow How do I use arrays in C++?
请输入您的翻译

How do I use arrays in C++?

确认取消