我如何声明一个2d数组使用新?

比如,对于一个“普通”数组,我将:

int* ary = new int[Size]

but

int** ary = new int[sizeY][sizeX]

A)不工作/编译b)没有完成什么:

int ary[sizeY][sizeX] 

所做的事。


当前回答

如果你想声明一个预定义的指针数组:

int **x;
x = new int*[2] { 
        new int[2] { 0, 1 },
        new int[2] { 2, 3 } 
    };

访问:

cout << x[0][0];

其他回答

尽管这个流行的答案将为您提供所需的索引语法,但它的效率是双重的:在空间和时间上都大而慢。有更好的办法。

为什么答案又大又慢

建议的解决方案是创建一个指针的动态数组,然后将每个指针初始化到它自己的独立动态数组。这种方法的优点是它提供了你习惯的索引语法,所以如果你想找到矩阵在x,y位置的值,你说:

int val = matrix[ x ][ y ];

这是因为矩阵[x]返回一个指向数组的指针,然后用[y]作为索引。分解一下:

int* row = matrix[ x ];
int  val = row[ y ];

方便,是吗?我们喜欢[x][y]语法。

但是这个解决方案有一个很大的缺点,那就是它既胖又慢。

Why?

The reason that it's both fat and slow is actually the same. Each "row" in the matrix is a separately allocated dynamic array. Making a heap allocation is expensive both in time and space. The allocator takes time to make the allocation, sometimes running O(n) algorithms to do it. And the allocator "pads" each of your row arrays with extra bytes for bookkeeping and alignment. That extra space costs...well...extra space. The deallocator will also take extra time when you go to deallocate the matrix, painstakingly free-ing up each individual row allocation. Gets me in a sweat just thinking about it.

它慢还有另一个原因。这些单独的分配往往位于内存的不连续部分。一行的地址可能是1000,另一行的地址可能是100000——你可以理解。这意味着当你在穿越矩阵时,你就像一个狂野的人一样在记忆中跳跃。这往往会导致缓存丢失,从而大大降低处理时间。

所以,如果你绝对必须有你可爱的[x][y]索引语法,使用这个解决方案。如果你想要快速和小巧(如果你不关心这些,为什么要用c++ ?),你需要一个不同的解决方案。

不同的解决方案

更好的解决方案是将整个矩阵分配为单个动态数组,然后使用自己的(稍微)聪明的索引数学来访问单元格。索引的数学运算非常巧妙;不,这一点也不聪明:这是显而易见的。

class Matrix
{
    ...
    size_t index( int x, int y ) const { return x + m_width * y; }
};

给定这个index()函数(我想象它是一个类的成员,因为它需要知道矩阵的m_width),您可以访问矩阵数组中的单元格。矩阵数组是这样分配的:

array = new int[ width * height ];

所以在缓慢的,高脂肪的溶液中

array[ x ][ y ]

...这是一个快速,小的解决方案:

array[ index( x, y )]

很难过,我知道。但你会习惯的。你的CPU会感谢你的。

在c++ 11及以上版本中,我推荐两种通用技术,一种用于编译时维度,另一种用于运行时维度。这两个答案都假设您需要统一的二维数组(而不是锯齿状数组)。

编译时维度

使用std::array的std::array,然后使用new把它放到堆上:

// the alias helps cut down on the noise:
using grid = std::array<std::array<int, sizeX>, sizeY>;
grid * ary = new grid;

同样,这仅适用于在编译时已知维度大小的情况。

运行时维度

实现只有在运行时才知道大小的二维数组的最佳方法是将其包装到一个类中。该类将分配一个1d数组,然后重载操作符[]来为第一个维度提供索引。 这是因为在c++中,2D数组是行为主的:

(摘自http://eli.thegreenplace.net/2015/memory-layout-of-multi-dimensional-arrays/)

连续的内存序列有利于提高性能,而且易于清理。下面是一个示例类,省略了很多有用的方法,但显示了基本思想:

#include <memory>

class Grid {
  size_t _rows;
  size_t _columns;
  std::unique_ptr<int[]> data;

public:
  Grid(size_t rows, size_t columns)
      : _rows{rows},
        _columns{columns},
        data{std::make_unique<int[]>(rows * columns)} {}

  size_t rows() const { return _rows; }

  size_t columns() const { return _columns; }

  int *operator[](size_t row) { return row * _columns + data.get(); }

  int &operator()(size_t row, size_t column) {
    return data[row * _columns + column];
  }
}

因此,我们用std::make_unique<int[]>(行*列)项创建一个数组。我们重载操作符[],它将为我们索引行。它返回一个int *,指向该行的开头,然后可以像对列一样对该行进行解引用。注意,make_unique在c++ 14中首次发布,但如果需要,可以在c++ 11中填充它。

对于这些类型的结构,重载operator()也是很常见的:

  int &operator()(size_t row, size_t column) {
    return data[row * _columns + column];
  }

从技术上讲,我在这里没有使用new,但是从std::unique_ptr<int[]>移动到int *并使用new/delete是很简单的。

试着这样做:

int **ary = new int* [sizeY];
for (int i = 0; i < sizeY; i++)
    ary[i] = new int[sizeX];

如果你想要一个2d的整数数组,它的元素在内存中是按顺序分配的,你必须像这样声明它

int (*intPtr)[n] = new int[x][n]

你可以用任何维数来代替x,但是n在两个地方必须相等。例子

int (*intPtr)[8] = new int[75][8];
intPtr[5][5] = 6;
cout<<intPtr[0][45]<<endl;

必须打印6。

在c++ 11中可以:

auto array = new double[M][N]; 

这样,内存就不会被初始化。要初始化它,可以这样做:

auto array = new double[M][N]();

示例程序(用"g++ -std=c++11"编译):

#include <iostream>
#include <utility>
#include <type_traits>
#include <typeinfo>
#include <cxxabi.h>
using namespace std;

int main()
{
    const auto M = 2;
    const auto N = 2;

    // allocate (no initializatoin)
    auto array = new double[M][N];

    // pollute the memory
    array[0][0] = 2;
    array[1][0] = 3;
    array[0][1] = 4;
    array[1][1] = 5;

    // re-allocate, probably will fetch the same memory block (not portable)
    delete[] array;
    array = new double[M][N];

    // show that memory is not initialized
    for(int r = 0; r < M; r++)
    {
        for(int c = 0; c < N; c++)
            cout << array[r][c] << " ";
        cout << endl;
    }
    cout << endl;

    delete[] array;

    // the proper way to zero-initialize the array
    array = new double[M][N]();

    // show the memory is initialized
    for(int r = 0; r < M; r++)
    {
        for(int c = 0; c < N; c++)
            cout << array[r][c] << " ";
        cout << endl;
    }

    int info;
    cout << abi::__cxa_demangle(typeid(array).name(),0,0,&info) << endl;

    return 0;
}

输出:

2 4 
3 5 

0 0 
0 0 
double (*) [2]