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

时间:2011-01-26 22:14:36

标签: c++ arrays pointers multidimensional-array c++-faq

C ++从C继承了数组,几乎无处不在。 C ++提供了更容易使用且更不容易出错的抽象(std::vector<T>自C ++ 98和std::array<T, n>以来C++11),因此对数组的需求并不像但是,当您阅读遗留代码或与使用C语言编写的库进行交互时,您应该牢牢掌握数组的工作原理。

本常见问题分为五个部分:

  1. arrays on the type level and accessing elements
  2. array creation and initialization
  3. assignment and parameter passing
  4. multidimensional arrays and arrays of pointers
  5. common pitfalls when using arrays
  6. 如果您觉得此常见问题解答中缺少重要内容,请写下答案并将其作为附加部分链接到此处。

    在下文中,“array”表示“C array”,而不是类模板std::array。假定了C声明符语法的基本知识。请注意,如下所示,newdelete的手动使用在面对异常时非常危险,但这是another FAQ的主题。

    <子> (注意:这是Stack Overflow's C++ FAQ的一个条目。如果您想批评在此表单中提供常见问题解答的想法,那么the posting on meta that started all this就是这样做的地方。在C++ chatroom中监控了这个问题,首先是FAQ的想法,所以你的答案很可能会被那些提出想法的人阅读。)

5 个答案:

答案 0 :(得分:287)

答案 1 :(得分:131)

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

多维数组

大多数程序员都熟悉命名的多维数组,但许多程序员并不知道多维数组也可以匿名创建。多维数组通常称为“数组数组”或“ true 多维数组”。

命名多维数组

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

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: | | | | | | | | | | |
          +---+---+---+---+---+

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

匿名指针数组

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

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;

答案 2 :(得分:85)

分配

由于没有特殊原因,不能将数组分配给彼此。请改用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不太可能表现得更好。如果有疑问,请测量。

虽然您无法直接分配数组,但可以分配包含数组成员的结构和类。这是因为赋值运算符的array members are copied 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,从模板中实例化不同的函数。您还可以编写从元素类型和大小中抽象出来的quite useful函数模板。

答案 3 :(得分:70)

答案 4 :(得分:67)