605

new を使用して 2 次元配列を宣言するにはどうすればよいですか?

同様に、「通常の」配列の場合、次のようにします。

int* ary = new int[Size]

しかし

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

a) 動作/コンパイルせず、b) 何を達成しない:

int ary[sizeY][sizeX] 

します。

4

26 に答える 26

856

行の長さがコンパイル時の定数である場合、C++11 では許可されます

auto arr2d = new int [nrows][CONSTANT];

この回答を参照してください。C++ の拡張機能として可変長配列を許可する gcc のようなコンパイラは、new ここに示すように使用して、C99 が許可するような完全な実行時可変配列次元機能を取得できますが、移植可能な ISO C++ は、可変である最初の次元のみに限定されます。

別の効率的なオプションは、別の回答が示すように、大きな 1 次元配列に手動で 2 次元インデックスを作成することです。これにより、実際の 2 次元配列と同じコンパイラの最適化が可能になります (たとえば、配列が互いにエイリアスを持たないことや重複しないことを証明またはチェックします)。


それ以外の場合は、配列へのポインターの配列を使用して、連続した 2D 配列のような 2D 構文を許可できますが、これは効率的な単一の大きな割り当てではありません。次のように、ループを使用して初期化できます。

int** a = new int*[rowCount];
for(int i = 0; i < rowCount; ++i)
    a[i] = new int[colCount];

上記の forcolCount= 5およびrowCount = 4は、次のようになります。

ここに画像の説明を入力

deleteポインターの配列を削除する前に、ループを使用して各行を個別に忘れないでください。別の回答の例。

于 2009-06-01T20:45:15.920 に答える
331
int** ary = new int[sizeY][sizeX]

次のようにする必要があります。

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

そして、クリーンアップは次のようになります。

for(int i = 0; i < sizeY; ++i) {
    delete [] ary[i];
}
delete [] ary;

EDIT: Dietrich Eppがコメントで指摘したように、これは正確には軽量のソリューションではありません。別のアプローチは、1 つの大きなメモリ ブロックを使用することです。

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

// ary[i][j] is then rewritten as
ary[i*sizeY+j]
于 2009-06-01T20:46:22.237 に答える
233

この一般的な回答で目的のインデックス作成構文が得られますが、スペースと時間の両方で大きくて遅いという二重の非効率性があります。もっと良い方法があります。

その答えが大きくて遅い理由

提案された解決策は、ポインターの動的配列を作成し、各ポインターを独自の独立した動的配列に初期化することです。このアプローチの利点は、使い慣れたインデックス構文を使用できることです。したがって、位置 x、y で行列の値を見つけたい場合は、次のように言います。

int val = matrix[ x ][ y ];

これが機能するのは、matrix[x] が配列へのポインターを返し、[y] でインデックスが付けられるためです。それを分解する:

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

便利ですよね?[ x ][ y ] 構文が気に入っています。

しかし、このソリューションには大きな欠点があります。つまり、太くて遅いということです。

なんで?

太くて遅い理由は実は同じです。マトリックスの各「行」は、個別に割り当てられた動的配列です。ヒープ割り当てを行うと、時間とスペースの両方でコストがかかります。アロケーターは、割り当てを行うのに時間がかかり、O(n) アルゴリズムを実行することがあります。また、アロケータは、ブックキーピングとアラインメントのために、各行配列に余分なバイトを「埋め込み」ます。その余分なスペースにはコストがかかります...まあ...余分なスペース。デアロケーターは、行列の割り当てを解除するときに余分な時間がかかり、個々の行の割り当てを苦労して解放します。考えただけで汗だく。

遅い理由は他にもあります。これらの個別の割り当ては、メモリの不連続な部分に存在する傾向があります。1 つの行がアドレス 1,000 にあり、別の行がアドレス 100,000 にある場合があります。これは、マトリックスをトラバースしているとき、野生の人のように記憶を飛び越えていることを意味します。これにより、処理時間が大幅に遅くなるキャッシュ ミスが発生する傾向があります。

したがって、かわいい [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はあなたに感謝します。

于 2015-03-03T20:43:09.180 に答える
124

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]
于 2013-04-26T14:57:10.417 に答える
62

静的配列の例から、ギザギザの配列ではなく、長方形の配列が必要だと思います。以下を使用できます。

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

次に、要素に次のようにアクセスできます。

ary[y*sizeX + x]

で delete[] を使用することを忘れないでくださいary

于 2009-06-01T21:13:58.887 に答える
56

C++11 以降では、コンパイル時の次元用と実行時の次元用の 2 つの一般的な手法をお勧めします。どちらの回答も、均一な 2 次元配列 (ギザギザの配列ではない) が必要であると想定しています。

コンパイル時のディメンション

std::arrayofを使用してからstd::array、 を使用newしてヒープに配置します。

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

繰り返しますが、これは次元のサイズがコンパイル時にわかっている場合にのみ機能します。

ランタイム ディメンション

実行時にしか分からないサイズの 2 次元配列を実現する最善の方法は、それをクラスにラップすることです。クラスは 1 次元の配列を割り当て、オーバーロードoperator []して最初の次元のインデックスを提供します。これが機能するのは、C++ では 2D 配列が行優先であるためです。

論理形式と 1 次元形式で示される行列

( 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[]>(rows * columns)したがって、エントリを含む配列を作成します。operator []行にインデックスを付けるオーバーロードを行います。行の先頭を指す を返します。int *これは、列に対して通常どおり逆参照できます。最初は C++14 で出荷されますmake_uniqueが、必要に応じて C++11 でポリフィルできます。

これらのタイプの構造体がオーバーロードoperator()することもよくあります。

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

技術的にはここでは使用していませんが、から移動して/を使用newするのは簡単です。std::unique_ptr<int[]>int *newdelete

于 2015-08-28T20:44:37.143 に答える
34

なぜ STL:vector を使わないのですか? とても簡単で、ベクトルを削除する必要はありません。

int rows = 100;
int cols = 200;
vector< vector<int> > f(rows, vector<int>(cols));
f[rows - 1][cols - 1] = 0; // use it like arrays

「配列」を初期化することもできます。デフォルト値を指定するだけです

const int DEFAULT = 1234;
vector< vector<int> > f(rows, vector<int>(cols, DEFAULT));

ソース: C/C++ で 2、3 (または多) 次元配列を作成する方法は?

于 2016-06-06T14:10:58.790 に答える
32

この質問は私を悩ませていました.ベクトルのベクトルや独自の配列インデックスをローリングするよりも優れた解決策がすでに存在する必要があるのは十分に一般的な問題です。

C++ に存在するはずのものが存在しない場合、最初に確認する場所はboost.orgです。そこにBoost多次元配列ライブラリmulti_arrayが見つかりました。multi_array_ref独自の 1 次元配列バッファーをラップするために使用できるクラスも含まれています。

于 2009-06-02T16:18:04.050 に答える
15

GNU C ++で連続した多次元配列を割り当てる方法は? 「標準」構文を機能させる GNU 拡張機能があります。

問題は演算子 new [] にあるようです。代わりに演算子 new を使用してください。

double (* in)[n][n] = new (double[m][n][n]);  // GNU extension

以上で、C 互換の多次元配列が得られます...

于 2012-01-12T15:32:32.250 に答える
13

typedef はあなたの友達です

戻って他の多くの回答を見た後、他の回答の多くはパフォーマンスの問題に苦しんでいるか、配列を宣言するために異常または面倒な構文を使用するか、配列にアクセスする必要があるため、より深い説明が必要であることがわかりました要素 (または上記のすべて)。

まず、この回答は、コンパイル時に配列の次元を知っていることを前提としています。そうする場合、これは最高のパフォーマンスを提供し、標準の配列構文を使用して配列要素にアクセスできるため、最適なソリューションです。

これにより最高のパフォーマンスが得られる理由は、すべての配列をメモリの連続したブロックとして割り当てるためです。つまり、ページ ミスが少なくなり、空間的局所性が向上する可能性があります。ループ内で割り当てを行うと、割り当てループが他のスレッドやプロセスによって (おそらく複数回) 中断される可能性があるため、または単純に、たまたま利用可能な小さな空のメモリブロックを埋めるアロケータ。

その他の利点は、単純な宣言構文と標準の配列アクセス構文です。

new を使用する C++ の場合:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {

typedef double (array5k_t)[5000];

array5k_t *array5k = new array5k_t[5000];

array5k[4999][4999] = 10;
printf("array5k[4999][4999] == %f\n", array5k[4999][4999]);

return 0;
}

または calloc を使用した C スタイル:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {

typedef double (*array5k_t)[5000];

array5k_t array5k = calloc(5000, sizeof(double)*5000);

array5k[4999][4999] = 10;
printf("array5k[4999][4999] == %f\n", array5k[4999][4999]);

return 0;
}
于 2009-06-01T20:59:55.030 に答える
5

これを試してください:

int **ary = new int* [sizeY];
for (int i = 0; i < sizeY; i++)
    ary[i] = new int[sizeX];
于 2009-06-01T20:45:45.137 に答える
2

ここで、2 つのオプションがあります。最初のものは、配列の配列またはポインターのポインターの概念を示しています。画像でわかるように、アドレスが連続しているため、私は 2 番目のアドレスを好みます。

ここに画像の説明を入力

#include <iostream>

using namespace std;


int main(){

    int **arr_01,**arr_02,i,j,rows=4,cols=5;

    //Implementation 1
    arr_01=new int*[rows];

    for(int i=0;i<rows;i++)
        arr_01[i]=new int[cols];

    for(i=0;i<rows;i++){
        for(j=0;j<cols;j++)
            cout << arr_01[i]+j << " " ;
        cout << endl;
    }


    for(int i=0;i<rows;i++)
        delete[] arr_01[i];
    delete[] arr_01;


    cout << endl;
    //Implementation 2
    arr_02=new int*[rows];
    arr_02[0]=new int[rows*cols];
    for(int i=1;i<rows;i++)
        arr_02[i]=arr_02[0]+cols*i;

    for(int i=0;i<rows;i++){
        for(int j=0;j<cols;j++)
            cout << arr_02[i]+j << " " ;
        cout << endl;
    }

    delete[] arr_02[0];
    delete[] arr_02;


    return 0;
}
于 2016-05-26T17:42:25.377 に答える
1

これは古い答えですが、C ++のこのような動的配列を宣言するのが好きです

int sizeY,sizeX = 10;
 //declaring dynamic 2d array:
    int **ary = new int*[sizeY];
    for (int i = 0; i < sizeY; i++) 
    {
     ary[i] = new int[sizeX];
   }

このように実行時にサイズを変更できます。これは c++ 98 でテストされています

于 2021-06-20T08:33:48.767 に答える
1

プロジェクトが CLI (Common Language Runtime Support)の場合:

次のように記述したときに取得するものではなく、配列クラスを使用できます。

#include <array>
using namespace std;

つまり、std 名前空間を使用して配列ヘッダーを含めるときに取得するアンマネージド配列クラスではなく、std 名前空間と配列ヘッダーで定義されたアンマネージド配列クラスではなく、CLI のマネージド クラス配列です。

このクラスを使用すると、任意のランクの配列を作成できます。

次のコードは、2 行 3 列の int 型の新しい 2 次元配列を作成し、「arr」という名前を付けます。

array<int, 2>^ arr = gcnew array<int, 2>(2, 3);

これで、配列内の要素に名前を付けてアクセスし、角括弧を1 つだけ[]書き、その中に行と列を追加し、コンマで区切ることができます,

以下の次のコードは、上記の前のコードで既に作成した配列の 2 行目と 1 列目の要素にアクセスします。

arr[0, 1]

この行だけを書き込むことは、そのセルの値を読み取ること、つまりこのセルの値を取得することですが、等号を追加すると=、そのセルに値を書き込むこと、つまりこのセルに値を設定しようとしています。もちろん +=、-=、*= および /= 演算子を数値のみ (int、float、double、__int16、__int32、__int64 など) に使用することもできますが、既に知っていることを確認してください。

プロジェクトがCLI でない場合は、std 名前空間のアンマネージ配列クラスを使用できます#include <array>が、問題は、この配列クラスが CLI 配列とは異なることです。^このタイプの配列の作成は、記号とgcnewキーワードを削除する必要があることを除いて、CLI と同じです。<>しかし残念なことに、括弧内の 2 番目の int パラメータは、配列のランクではなく、長さ (つまりサイズ)を指定します!

この種のアレイでランクを指定する方法はありません。ランクは CLI アレイのみの機能です。.

std 配列は、ポインターを使用して定義する C++ の通常の配列のように動作します。たとえばint*、 and then: new int[size]、またはポインターなし:int arr[size]ですが、c++ の通常の配列とは異なり、std 配列は配列の要素で使用できる関数を提供します。 fill、begin、end、size などと同様ですが、通常の配列は何も提供しません。

ただし、標準配列は通常の C++ 配列と同様に 1 次元配列です。しかし、通常の C++ の 1 次元配列を 2 次元配列にする方法について他の人が提案した解決策のおかげで、同じアイデアを std 配列に適応させることができます。たとえば、Mehrdad Afshari のアイデアに従って、次のコードを書くことができます。

array<array<int, 3>, 2> array2d = array<array<int, 3>, 2>();

このコード行は、「ジャグ配列」を作成します。これは、各セルが別の 1 次元配列であるか、別の 1 次元配列を指している 1 次元配列です。

1 次元配列内のすべての 1 次元配列の長さ/サイズが等しい場合、array2d 変数を実際の 2 次元配列として扱うことができます。また、表示方法に応じて、特別なメソッドを使用して行または列を扱うことができます。 2D 配列では、std 配列がサポートしていることに注意してください。

Kevin Loney のソリューションを使用することもできます。

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

// ary[i][j] is then rewritten as
ary[i*sizeY+j]

ただし、std 配列を使用する場合、コードは異なって見える必要があります。

array<int, sizeX*sizeY> ary = array<int, sizeX*sizeY>();
ary.at(i*sizeY+j);

そして、std 配列の独自の機能をまだ持っています。

[]かっこを使用して std 配列の要素に引き続きアクセスでき、関数を呼び出す必要がないことに注意してくださいat。また、繰り返しの代わりに、std 配列の要素の総数を計算して保持し、その値を使用する新しい int 変数を定義して割り当てることもできます。sizeX*sizeY

独自の 2 次元配列ジェネリック クラスを定義し、新しい 2 次元配列の行と列の数を指定する 2 つの整数を受け取る 2 次元配列クラスのコンストラクターを定義し、整数の 2 つのパラメーターを受け取る get 関数を定義できます。最初の 2 つは 2 次元配列の行と列を指定する整数であり、3 番目のパラメータはエレメント。その型は、ジェネリック クラスで選択した型によって異なります。

通常の c++ 配列 (ポインターの有無にかかわらず)またはstd 配列を使用してこれらすべてを実装し、他の人が提案したアイデアの 1 つを使用して、cli 配列のように、または 2 つのように使いやすくすることができます。 C# で定義、割り当て、使用できる次元配列。

于 2014-09-04T22:36:00.430 に答える
0

場合によっては、私にとって最も効果的な解決策を残しました。特に、配列の 1 つの次元 [のサイズ?] を知っている場合。たとえば、char[20] の配列のさまざまなサイズの配列が必要な場合など、char の配列に非常に役立ちます。

int  size = 1492;
char (*array)[20];

array = new char[size][20];
...
strcpy(array[5], "hola!");
...
delete [] array;

キーは、配列宣言の括弧です。

于 2014-01-09T15:18:29.880 に答える
-3

動的配列を作成するときにこれを使用しています。クラスまたは構造体がある場合。そして、これは機能します。例:

struct Sprite {
    int x;
};

int main () {
   int num = 50;
   Sprite **spritearray;//a pointer to a pointer to an object from the Sprite class
   spritearray = new Sprite *[num];
   for (int n = 0; n < num; n++) {
       spritearray[n] = new Sprite;
       spritearray->x = n * 3;
  }

   //delete from random position
    for (int n = 0; n < num; n++) {
        if (spritearray[n]->x < 0) {
      delete spritearray[n];
      spritearray[n] = NULL;
        }
    }

   //delete the array
    for (int n = 0; n < num; n++) {
      if (spritearray[n] != NULL){
         delete spritearray[n];
         spritearray[n] = NULL;
      }
    }
    delete []spritearray;
    spritearray = NULL;

   return 0;
  } 
于 2015-03-03T20:20:21.757 に答える