2D配列をC ++関数に渡す


324

可変サイズの2D配列をパラメーターとして取りたい関数があります。

これまでのところ私はこれを持っています:

void myFunction(double** myArray){
     myArray[x][y] = 5;
     etc...
}

そして、私は私のコードの他の場所で配列を宣言しました:

double anArray[10][10];

ただし、電話myFunction(anArray)をかけるとエラーが発生します。

渡すときに配列をコピーしたくありません。変更を加えるとmyFunction、の状態が変わるはずですanArray。私が正しく理解していれば、2D配列へのポインタを引数として渡したいだけです。関数は、異なるサイズの配列も受け入れる必要があります。たとえば、[10][10]そして[5][5]。これどうやってするの?


1
パラメータ3を「double [10] [10]」から「double **」に変換できません
RogerDarwin

3
受け入れ応答のみを示し2技術[その(2)及び(3)が、存在している同じである] 関数に2次元配列を渡すの4つのユニークな方法
legends2k 2014年

厳密に言えば、はい、それらは2D配列ではありませんが、(UBにつながるものの)ポインターの配列を持ち、それぞれが(1D)配列を指すという規則が普及しているようです:(mxnのフラット化された1D配列がある長さ、2D配列をエミュレートするヘルパー関数/クラスを使用した方が良いかもしれません
legends2k

最も簡単 - func(int* mat, int r, int c){ for(int i=0; i<r; i++) for(int j=0; j<c; j++) printf("%d ", *(mat+i*c+j)); }。次のように呼びますint mat[3][5]; func(mat[0], 3, 5);
Minhas Kamal

回答:


413

2D配列を関数に渡す方法は3つあります。

  1. パラメータは2D配列です

    int array[10][10];
    void passFunc(int a[][10])
    {
        // ...
    }
    passFunc(array);
  2. パラメータはポインタを含む配列です

    int *array[10];
    for(int i = 0; i < 10; i++)
        array[i] = new int[10];
    void passFunc(int *a[10]) //Array containing pointers
    {
        // ...
    }
    passFunc(array);
  3. パラメータはポインタへのポインタです

    int **array;
    array = new int *[10];
    for(int i = 0; i <10; i++)
        array[i] = new int[10];
    void passFunc(int **a)
    {
        // ...
    }
    passFunc(array);

4
あなたはの要素を取得することができます@Overflowh arrayarray[i][j]:)
shengy

14
最初のケースでは、パラメータはとして宣言できますint (*a)[10]
ザカリー2013年

9
2番目の場合、パラメータはとして宣言できますint **
ザカリー2013年

1
@ザック:その通りです、実際には2つのケースしかありません。1つはポインタツーポインタで、もう1つはサイズn ieの整数配列への単一のポインタint (*a) [10]です。
legends2k 2013

3
ケース2と3は2D配列ではないため、この答えは誤解を招きます。こちらをご覧ください
ランディン

178

固定サイズ

1.参照渡し

template <size_t rows, size_t cols>
void process_2d_array_template(int (&array)[rows][cols])
{
    std::cout << __func__ << std::endl;
    for (size_t i = 0; i < rows; ++i)
    {
        std::cout << i << ": ";
        for (size_t j = 0; j < cols; ++j)
            std::cout << array[i][j] << '\t';
        std::cout << std::endl;
    }
}

C ++では、呼び出し元が誤った次元(不一致の場合はコンパイラフラグ)を渡すことを心配する必要がないため、次元情報を失わずに参照によって配列を渡すことがおそらく最も安全です。ただし、これは動的(フリーストア)配列では不可能です。自動(通常はスタックリビング)配列に対してのみ機能します。つまり、次元数はコンパイル時に認識される必要があります。

2.ポインターで渡す

void process_2d_array_pointer(int (*array)[5][10])
{
    std::cout << __func__ << std::endl;
    for (size_t i = 0; i < 5; ++i)
    {
        std::cout << i << ": ";
        for (size_t j = 0; j < 10; ++j)
            std::cout << (*array)[i][j] << '\t';
        std::cout << std::endl;
    }    
}

前のメソッドと同等のCは、ポインターによって配列を渡します。これは、配列のディケイポインタータイプ(3)による受け渡しと混同しないでください。これは、一般的な一般的な方法ですが、これよりも安全ではありませんが、柔軟性があります。(1)と同様に、配列のすべての次元が固定されており、コンパイル時に既知である場合に、このメソッドを使用します。関数を呼び出すときprocess_2d_array_pointer(&a)は、最初の要素のアドレスではなく配列のアドレスをdecayで渡す必要があることに注意してくださいprocess_2d_array_pointer(a)

可変サイズ

これらはCから継承されますが、安全性は低く、コンパイラーはチェックする方法がないため、呼び出し元が必要なディメンションを確実に渡しています。関数は、呼び出し元が次元として渡すものにのみ依存します。これらは、長さが異なる配列を常に渡すことができるため、上記のものよりも柔軟性があります。

配列をCの関数に直接渡すことなどないことを覚えておいてください[C ++では、参照として渡すことができます(1) ]。(2)は、配列自体ではなく配列へのポインタを渡します。常に配列をそのまま渡すと、ポインターのコピー操作となり、配列の性質がポインターに変化するため、この操作が容易になります

3.減衰した型へのポインタを(値)で渡します

// int array[][10] is just fancy notation for the same thing
void process_2d_array(int (*array)[10], size_t rows)
{
    std::cout << __func__ << std::endl;
    for (size_t i = 0; i < rows; ++i)
    {
        std::cout << i << ": ";
        for (size_t j = 0; j < 10; ++j)
            std::cout << array[i][j] << '\t';
        std::cout << std::endl;
    }
}

int array[][10]許可されている上記の構文は、それが識別子があることが明らかになり以来、私は上記の構文上でそれをお勧めしませんしたいarray10個の整数の配列への単一のポインタで、この構文ながら、ルックス、それは2次元配列ですが、に同じポインタであるように10個の整数の配列。ここでは、1つの行の要素数(つまり、列サイズ、ここでは10)はわかっていますが、行数は不明なので、引数として渡されます。この場合、2番目の次元が10でない配列へのポインターが渡されたときにコンパイラーがフラグを立てることができるので、ある程度の安全があります。最初の次元は可変部分であり、省略できます。最初の次元のみを省略できる理由については、こちらをご覧ください

4.ポインターをポインターに渡す

// int *array[10] is just fancy notation for the same thing
void process_pointer_2_pointer(int **array, size_t rows, size_t cols)
{
    std::cout << __func__ << std::endl;
    for (size_t i = 0; i < rows; ++i)
    {
        std::cout << i << ": ";
        for (size_t j = 0; j < cols; ++j)
            std::cout << array[i][j] << '\t';
        std::cout << std::endl;
    }
}

繰り返しますint *array[10]が、と同じ構文がありますint **array。この構文で[10]は、ポインタに減衰するため、は無視され、になりint **arrayます。おそらく、行数が必要な場合でも、渡された配列に少なくとも10列が必要であることは、呼び出し元への手掛かりにすぎません。いずれの場合でも、コンパイラーは長さ/サイズ違反のフラグを立てません(渡された型がポインターへのポインターであるかどうかのみをチェックします)。そのため、ここではパラメーターとして行数と列数の両方が必要になります。

注: (4)は最も安全性の低いオプションです型チェックがほとんどなく、最も不便であるため、最もです。この関数に2D配列を正当に渡すことはできません。C-FAQは非難することの通常の回避策をint x[5][10]; process_pointer_2_pointer((int**)&x[0][0], 5, 10);それとして潜在的に未定義の動作につながる可能性による配列平坦化にします。このメソッドで配列を渡す正しい方法は不便な部分をもたらします。つまり、各要素が実際の渡される配列のそれぞれの行を指す、ポインタの追加(代理)配列が必要です。次に、このサロゲートが関数に渡されます(以下を参照)。これは、上記の方法と同じ作業を行うためのすべてであり、より安全で、クリーンで、おそらくより高速です。

上記の機能をテストするためのドライバープログラムを次に示します。

#include <iostream>

// copy above functions here

int main()
{
    int a[5][10] = { { } };
    process_2d_array_template(a);
    process_2d_array_pointer(&a);    // <-- notice the unusual usage of addressof (&) operator on an array
    process_2d_array(a, 5);
    // works since a's first dimension decays into a pointer thereby becoming int (*)[10]

    int *b[5];  // surrogate
    for (size_t i = 0; i < 5; ++i)
    {
        b[i] = a[i];
    }
    // another popular way to define b: here the 2D arrays dims may be non-const, runtime var
    // int **b = new int*[5];
    // for (size_t i = 0; i < 5; ++i) b[i] = new int[10];
    process_pointer_2_pointer(b, 5, 10);
    // process_2d_array(b, 5);
    // doesn't work since b's first dimension decays into a pointer thereby becoming int**
}

動的に割り当てられた配列をC ++の関数に渡すのはどうですか?C11標準では、fn(int col、int row、int array [col] [row])のような静的および動的に割り当てられた配列に対して実行できます。stackoverflow.com/ questions / 16004668 / …この問題について質問しました:stackoverflow.com/questions/27457076/...
42n4

@ 42n4ケース4は(C ++の場合も)それをカバーします。動的に割り当てられた配列の場合、ループ内の行だけがからb[i] = a[i];に変わりb[i] = new int[10];ます。一つは、また作ることがb動的に割り当てられint **b = int *[5];、それはまだそのままで動作します。
legends2k 2014

1
4)array[i][j]の関数でアドレッシングはどのように機能しますか?それはptrからptrを受け取り、最後の次元の値を知らないので、正しいアドレス指定のためにシフトを実行するために必要ですか?
user1234567 2014

2
array[i][j]これは単なるポインタ演算、つまりポインタの値に対するものであり、結果をとしてarray追加iおよび逆参照し、int*そこにjその位置を追加および逆参照し、を読み取りますint。ですから、いいえ、それはこのための次元を知る必要はありません。しかし、それがすべてのポイントです!コンパイラーはプログラマーの言葉を忠実に理解し、プログラマーが正しくなかった場合、未定義の動作が発生します。これが、ケース4が最も安全でないオプションであると述べた理由です。
legends2k 2014

このような場合、構造体が役立ちます。
Xofo 2018年

40

shengyの最初の提案に対する変更です。テンプレートを使用して、関数に多次元配列変数を受け入れさせることができます(管理および削除する必要のあるポインターの配列を格納する代わりに)。

template <size_t size_x, size_t size_y>
void func(double (&arr)[size_x][size_y])
{
    printf("%p\n", &arr);
}

int main()
{
    double a1[10][10];
    double a2[5][5];

    printf("%p\n%p\n\n", &a1, &a2);
    func(a1);
    func(a2);

    return 0;
}

printステートメントは、配列が参照によって渡されていることを示すためにあります(変数のアドレスを表示することにより)


2
を使用%pしてポインタを出力する必要があります。それでも、それをvoid *にキャストする必要がありprintf()ます。それ以外の場合は、未定義の動作を呼び出します。さらに、&関数を呼び出すときにaddressof()演算子を使用しないでください。これは、関数がtypeの引数を期待しているのdouble (*)[size_y]に対し、現在はdouble (*)[10][10]andを渡しているためdouble (*)[5][5]です。

テンプレートを使用している場合は、テンプレートの引数として両方の次元を作成する方が適切であり、低レベルのポインターアクセスを完全に回避できるため、より優れています。
legends2k 2014年

3
これは、コンパイル時に配列のサイズがわかっている場合にのみ機能します。
jeb_is_a_mess 2017

上記の@Georgコードはまさに私が提案したものです。GCC 6.3- オンラインデモで動作します。パラメータを参照にすることを忘れましたか?
legends2k

21

まだ誰もこれについて言及していないことに驚いていますが、[] []セマンティクスをサポートする2Dのテンプレートを作成できます。

template <typename TwoD>
void myFunction(TwoD& myArray){
     myArray[x][y] = 5;
     etc...
}

// call with
double anArray[10][10];
myFunction(anArray);

などの2Dの「配列のような」データ構造std::vector<std::vector<T>>、またはユーザー定義型と連携して、コードの再利用を最大化します。


1
これが正解です。言及されたすべての問題と、ここでは言及されなかったいくつかの問題を解決します。型の安全性、コンパイル時の配列の非互換性、ポインター演算なし、型キャストなし、データコピーなし。CおよびC ++で動作します。
OpalApps 2018

まあ、これはC ++で機能します。Cはテンプレートをサポートしていません。Cで行うにはマクロが必要です。
Gunnar

20

次のような関数テンプレートを作成できます。

template<int R, int C>
void myFunction(double (&myArray)[R][C])
{
    myArray[x][y] = 5;
    etc...
}

次に、RとCを介して両方の次元サイズがあります。配列サイズごとに異なる関数が作成されるため、関数が大きく、さまざまな異なる配列サイズで呼び出す場合、これはコストがかかる可能性があります。ただし、次のような関数のラッパーとして使用できます。

void myFunction(double * arr, int R, int C)
{
    arr[x * C + y] = 5;
    etc...
}

配列を1次元として扱い、算術を使用してインデックスのオフセットを計算します。この場合、テンプレートを次のように定義します。

template<int C, int R>
void myFunction(double (&myArray)[R][C])
{
    myFunction(*myArray, R, C);
}

2
size_tは、配列インデックスのタイプよりも優れていますint
Andrew Tomazos 2013

13

anArray[10][10]はポインタへのポインタではありません。これはdouble型の100個の値を格納するのに適したメモリの連続したチャンクであり、次元を指定したため、コンパイラはアドレス指定方法を知っています。これを配列として関数に渡す必要があります。次のように、初期次元のサイズを省略できます。

void f(double p[][10]) {
}

ただし、これでは、最後の次元が10以外の配列を渡すことはできません。

C ++での最善の解決策は、使用するstd::vector<std::vector<double> >ことです。これは、ほぼ同じくらい効率的で、はるかに便利です。


1
stdライブラリは非常に効率的であるため、私はこの解決策を好みます。dasblinkenlightが好きです。以前はdasblikenlichtを使用していました
mozillanerd

ほぼ同じくらい効率的ですか?ええ、その通り。ポインター追跡は常に非ポインター追跡よりも高価です。
Thomas Eding、2015

8

1次元配列は、配列の最初の要素を指すポインターポインターに減衰します。2D配列は最初の行を指すポインタに減衰します。したがって、関数プロトタイプは-

void myFunction(double (*myArray) [10]);

私はstd::vector生の配列よりも好みます。


8

あなたはこのようなことをすることができます...

#include<iostream>

using namespace std;

//for changing values in 2D array
void myFunc(double *a,int rows,int cols){
    for(int i=0;i<rows;i++){
        for(int j=0;j<cols;j++){
            *(a+ i*rows + j)+=10.0;
        }
    }
}

//for printing 2D array,similar to myFunc
void printArray(double *a,int rows,int cols){
    cout<<"Printing your array...\n";
    for(int i=0;i<rows;i++){
        for(int j=0;j<cols;j++){
            cout<<*(a+ i*rows + j)<<"  ";
        }
    cout<<"\n";
    }
}

int main(){
    //declare and initialize your array
    double a[2][2]={{1.5 , 2.5},{3.5 , 4.5}};

    //the 1st argument is the address of the first row i.e
    //the first 1D array
    //the 2nd argument is the no of rows of your array
    //the 3rd argument is the no of columns of your array
    myFunc(a[0],2,2);

    //same way as myFunc
    printArray(a[0],2,2);

    return 0;
}

出力は次のようになります...

11.5  12.5
13.5  14.5

1
この場合に配列をマングルする理由を思いつくことができる唯一の理由は、配列ポインターの仕組みに関する知識が不足しているためです。
ランディン

3
この場合のように列と行が等しい場合を除き、i変数は行ではなく列で乗算する必要があります
Andrey Chernukha

4

これはベクトル行列の例のベクトルです

#include <iostream>
#include <vector>
using namespace std;

typedef vector< vector<int> > Matrix;

void print(Matrix& m)
{
   int M=m.size();
   int N=m[0].size();
   for(int i=0; i<M; i++) {
      for(int j=0; j<N; j++)
         cout << m[i][j] << " ";
      cout << endl;
   }
   cout << endl;
}


int main()
{
    Matrix m = { {1,2,3,4},
                 {5,6,7,8},
                 {9,1,2,3} };
    print(m);

    //To initialize a 3 x 4 matrix with 0:
    Matrix n( 3,vector<int>(4,0));
    print(n);
    return 0;
}

出力:

1 2 3 4
5 6 7 8
9 1 2 3

0 0 0 0
0 0 0 0
0 0 0 0

2

2D配列を関数に渡すには、いくつかの方法があります。

  • 単一のポインター使用して、2D配列を型キャストする必要があります。

    #include<bits/stdc++.h>
    using namespace std;
    
    
    void func(int *arr, int m, int n)
    {
        for (int i=0; i<m; i++)
        {
           for (int j=0; j<n; j++)
           {
              cout<<*((arr+i*n) + j)<<" ";
           }
           cout<<endl;
        }
    }
    
    int main()
    {
        int m = 3, n = 3;
        int arr[m][n] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
        func((int *)arr, m, n);
        return 0;
    }
  • ダブルポインターの使用この方法で、2次元配列も型キャストします。

    #include<bits/stdc++.h>
    using namespace std;

   void func(int **arr, int row, int col)
   {
      for (int i=0; i<row; i++)
      {
         for(int j=0 ; j<col; j++)
         {
           cout<<arr[i][j]<<" ";
         }
         printf("\n");
      }
   }

  int main()
  {
     int row, colum;
     cin>>row>>colum;
     int** arr = new int*[row];

     for(int i=0; i<row; i++)
     {
        arr[i] = new int[colum];
     }

     for(int i=0; i<row; i++)
     {
         for(int j=0; j<colum; j++)
         {
            cin>>arr[i][j];
         }
     }
     func(arr, row, colum);

     return 0;
   }

1

多次元配列を渡すための1つの重要なことは、次のとおりです。

  • First array dimension 指定する必要はありません。
  • Second(any any further)dimension 指定する必要があります。

1. 2番目の次元のみがグローバルに使用できる場合(マクロまたはグローバル定数として)

`const int N = 3;

`void print(int arr[][N], int m)
{
int i, j;
for (i = 0; i < m; i++)
  for (j = 0; j < N; j++)
    printf("%d ", arr[i][j]);
}`

int main()
{
int arr[][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
print(arr, 3);
return 0;
}`

2.単一ポインタの使用:このメソッドでは、関数に渡すときに2D配列を型キャストする必要があります。

`void print(int *arr, int m, int n)
{
int i, j;
for (i = 0; i < m; i++)
  for (j = 0; j < n; j++)
    printf("%d ", *((arr+i*n) + j));
 }

`int main()
{
int arr[][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int m = 3, n = 3;

// We can also use "print(&arr[0][0], m, n);"
print((int *)arr, m, n);
return 0;
}`

0

これを行うには、C ++のテンプレート機能を使用できます。私はこのようなことをしました:

template<typename T, size_t col>
T process(T a[][col], size_t row) {
...
}

このアプローチの問題は、提供するcolのすべての値に対して、新しい関数定義がテンプレートを使用してインスタンス化されることです。そう、

int some_mat[3][3], another_mat[4,5];
process(some_mat, 3);
process(another_mat, 4);

テンプレートを2回インスタンス化して、2つの関数定義を生成します(1つはcol = 3、もう1つはcol = 5)。


0

合格int a[2][3]したい場合はvoid func(int** pp)、次のような補助手順が必要です。

int a[2][3];
int* p[2] = {a[0],a[1]};
int** pp = p;

func(pp);

最初のもの[2]は暗黙的に指定できるので、さらに簡略化できます。

int a[][3];
int* p[] = {a[0],a[1]};
int** pp = p;

func(pp);

0

動的サイズの2次元配列を関数に渡したい場合は、いくつかのポインターを使用するとうまくいきます。

void func1(int *arr, int n, int m){
    ...
    int i_j_the_element = arr[i * m + j];  // use the idiom of i * m + j for arr[i][j] 
    ...
}

void func2(){
    ...
    int arr[n][m];
    ...
    func1(&(arr[0][0]), n, m);
}

0

左端の次元を省略できるため、2つのオプションが生じます。

void f1(double a[][2][3]) { ... }

void f2(double (*a)[2][3]) { ... }

double a[1][2][3];

f1(a); // ok
f2(a); // ok 

これはポインタでも同じです:

// compilation error: cannot convert ‘double (*)[2][3]’ to ‘double***’ 
// double ***p1 = a;

// compilation error: cannot convert ‘double (*)[2][3]’ to ‘double (**)[3]’
// double (**p2)[3] = a;

double (*p3)[2][3] = a; // ok

// compilation error: array of pointers != pointer to array
// double *p4[2][3] = a;

double (*p5)[3] = a[0]; // ok

double *p6 = a[0][1]; // ok

N次元配列からN-1次元配列へのポインタへの減衰は、C ++標準で許可されていますでています。これは、左端の次元が失われても、N-1次元情報を持つ配列要素に正しくアクセスできるためです。

詳細はこちら

ただし、配列とポインタは同じではありません。配列はポインタに崩壊する可能性がありますが、ポインタは、それが指すデータのサイズ/構成に関する状態を伝えません。

A char **、それ自体が文字のメモリブロックを指す文字ポインタを含むメモリブロックへのポインタです。A char [][]文字を含む単一のメモリブロックです。これは、コンパイラーがコードを変換する方法と最終的なパフォーマンスがどのようになるかに影響を与えます。

ソース

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.