web-dev-qa-db-ja.com

CおよびC ++の関数に多次元配列を渡す方法

#include<stdio.h>
void print(int *arr[], int s1, int s2) {
    int i, j;
    for(i = 0; i<s1; i++)
        for(j = 0; j<s2; j++)
            printf("%d, ", *((arr+i)+j));
}

int main() {
    int a[4][4] = {{0}};
    print(a,4,4);
}

これはCでは機能しますが、C++では機能しません。

エラー:

cannot convert `int (*)[4]' to `int**' for argument `1' to 
`void print(int**, int, int)'

なぜC++では機能しないのですか?どのような変更を加える必要がありますか?

42
Moeb

このコードは、CまたはC++でnot動作します。タイプ_int[4][4]_の配列は、タイプ_int **_のポインターに変換できません(これは、パラメーター宣言で_int *arr[]_が表すものです)。 Cでcompileできたのは、C++コンパイラから取得したエラーメッセージと基本的に同じ形式のCコンパイラ警告をおそらく無視したためです。 (Cコンパイラはwarningsを本質的にerrorに対して発行することがあります。)

ですから、再び、真実ではない主張をしないでください。このコードはCでは機能しません。組み込みの2D配列を_int **_ポインターに変換するには、次のような手法を使用できます。

C++での多次元配列のポインターへの変換

(受け入れられた答えを参照してください。問題はまったく同じです。)

EDIT:コードが表示されますCで動作するため、印刷コードの別のバグが効果を装っている配列渡しのバグの_int **_擬似配列の要素に適切にアクセスするには、式*(*(arr + i) + j)を使用するか、より良いプレーン_arr[i][j]_(同じもの)を使用する必要があります。余分な_*_を見逃したため、配列の内容とはまったく関係のないものが出力されました。繰り返しますが、mainの配列を他の何かに初期化して、Cで印刷している結果が、意図した配列の内容とまったく関係がないことを確認します。

上記のようにprintfステートメントを変更すると、最初に説明した配列渡しのバグが原因でコードがクラッシュする可能性が高くなります。

もう一度:_int[4][4]_配列を_int **_疑似配列として渡すことはできません。これは、C++がエラーメッセージで伝えていることです。そして、これはあなたのCコンパイラがあなたに言ったことだと確信していますが、それはおそらく「単なる警告」だったので無視したでしょう。

30
AnT

問題はそれです

int a[4][4];

実際には物理的に連続したメモリに保存されます。したがって、4x4配列の任意の部分にアクセスするには、関数「print」は配列の次元を知る必要があります。たとえば、次の小さなコードは、2つの異なる方法でメモリの同じ部分にアクセスします。

#include <iostream>

void print(int a[][4]) {
    for (int i = 0; i <4; i++) {
        for (int j = 0; j < 4; j++) {
            //accessing as 4x4 array
            std::cout << a[i][j] <<std::endl;        

            //accessing corresponding to the physical layout in memory
            std::cout <<  *(*(a)+ i*4 + j) << std::endl;  

        }
    }
}

int main() {
    int a[4][4];

    //populating the array with the corresponding indices from 0 to 15
    int m = 0;
    for (int i = 0; i<4; i++) {
        for (int j= 0; j < 4; j++) {
            a[i][j] =  m;
            m++;
        }
    }
    print(a);
}

したがって、メモリレイアウトは変わりませんが、アクセス方法は変わります。チェッカーボードのように視覚化できます。

   0  1  2  3
  ----------
0| 1  2  3  4
1| 5  6  7  8
2| 9 10 11 12
3|13 14 15 16

しかし、実際の物理メモリはこのように見えます。

0*4+0 0*4+1 0*4+2 0*4+3 1*4+0 1*4+1 1*4+2 1*4+3 2*4+1   etc.
-----------------------------------------------------
1      2       3    4     5     6      7     8     9    etc.

C++では、配列のデータは行ごとに格納され、次の行の適切なメモリオフセットを取得するには行の長さ(この場合は4)が常に必要です。したがって、最初の添え字は、配列が宣言されたときに必要なストレージの量のみを示しますが、後でオフセットを計算する必要はありません。

18
Lucas
#include<stdio.h>
void print(int arr[][4], int s1, int s2) {
    int i, j;
    printf("\n");
    for(i = 0; i<s1; i++) {
        for(j = 0; j<s2; j++) {
            printf("%d, ", *((arr+i)+j));
        }
    }
    printf("\n");
}

int main() {
    int a[4][4] = {{0}};
    print(a,4,4);
}

これは機能しますが、作業とはコンパイルを意味します。 @AndreyTは、バージョンがまだ機能しない理由を説明しました。

これは、2次元配列を渡す方法です。

明確にするために、関数宣言で両方のサイズを指定することもできます。

#include<stdio.h>
void print(int arr[4][4], int s1, int s2) {
    int i, j;
    printf("\n");
    for(i = 0; i<s1; i++) {
        for(j = 0; j<s2; j++) {
            printf("%d, ", *((arr+i)+j));
        }
    }
    printf("\n");
}

int main() {
    int a[4][4] = {{0}};
    print(a,4,4);
}

両方とも機能します。

jith要素にアクセスする場合は、*((arr+i)+j)a[i][j](できれば)または*(*(arr+i)+j)に変更する必要もあります。

8
IVlad

両方とも動作しているが、理論的には無効なバージョン(以下を参照)のC90とC++ 98を次に示します。

#include <stdio.h>

static void print(int *arr, size_t s1, size_t s2)
{
    size_t i, j;
    printf("\n");
    for(i = 0; i < s1; i++) {
        for(j = 0; j < s2; j++) {
            printf("%d, ", arr[i * s2 + j]);
        }
    }
    printf("\n");
}

int main(void) {
    int a[4][4] = {{0}};
    print(a[0], 4, 4);
    return 0;
}

テンプレートを使用するC++バージョン( Notinlist's answer から適応)は、次のようになります。

#include <iostream>
#include <cstring>

using namespace std;

template <size_t N, size_t M>
struct IntMatrix
{
    int data[N][M];
    IntMatrix() { memset(data, 0, sizeof data); }
};

template <size_t N, size_t M>
ostream& operator<<(ostream& out, const IntMatrix<N,M>& m)
{
    out << "\n";
    for(size_t i = 0; i < N; i++) {
        for(size_t j = 0; j < M; j++) {
            out << m.data[i][j] << ", ";
        }
    }
    out << "\n";
    return out;
}

int main()
{
    IntMatrix<4,4> a;
    cout << a;
    return 0;
}

または、ネストされたSTLコンテナを使用することもできます-vector< vector<int> >-プレーン配列の代わり。

C99では、次のことができます。

static void print(size_t s1, size_t s2, int arr[s1][s2]) {
    printf("\n");
    for(size_t i = 0; i < s1; i++) {
        for(size_t j = 0; j < s2; j++) {
            printf("%d, ", arr[i][j]);
        }
    }
    printf("\n");
}

そしてそれを

print(4, 4, a);

ロバートがコメントで指摘したように、最初のスニペットには実際には未定義の動作が含まれます。ただし、未定義の動作が関与している場合でも(コンピューターを爆破しない場合でも)ポインター演算により常にポインターが得られると仮定すると、標準内の他の制限のために可能な結果は1つだけです。標準では、何かが不必要に未定義のままになります。

私が知る限り、代わりに

print(a[0], 4, 4);

union m2f { int multi[4][4]; int flat[16]; } *foo = (union m2f *)&a;
print(foo->flat, 4, 4);

cを合法にします。

6
Christoph

代わりにint**を使用できます。そのはるかに柔軟性:

#include <stdio.h>
#include <stdlib.h>
void print(int **a, int numRows, int numCols )
{
  int row, col ;
  for( int row = 0; row < numRows; row++ )
  {
    for( int col = 0; col < numCols ; col++ )
    {
      printf("%5d, ", a[row][col]);
    }
    puts("");
  }
}

int main()
{
  int numRows = 16 ;
  int numCols = 5 ;
  int **a ;

  // a will be a 2d array with numRows rows and numCols cols

  // allocate an "array of arrays" of int
  a = (int**)malloc( numRows* sizeof(int*) ) ;

  // each entry in the array of arrays of int
  // isn't allocated yet, so allocate it
  for( int row = 0 ; row < numRows ; row++ )
  {
    // Allocate an array of int's, at each
    // entry in the "array of arrays"
    a[row] = (int*)malloc( numCols*sizeof(int) ) ;
  }

  int count = 1 ;
  for( int row = 0 ; row < numRows ; row++ )
  {
    for( int col = 0 ; col < numCols ; col++ )
    {
      a[row][col] = count++ ;
    }
  }

  print( a, numRows, numCols );
}

別物 あなたが興味があるかもしれないのは D3DMATRIX のような構造です:

 typedef struct _D3DMATRIX {
 union {
 struct {
 float _11、_12、_13、_14; 
 float _21、_22、_23、_24 ; 
 float _31、_32、_33、_34; 
 float _41、_42、_43、_44; 
 
}; 
 float m [4 ] [4]; 
}; 
} D3DMATRIX; 
 
 D3DMATRIX myMatrix; 

この小さなヒントの良い点は、両方のmyMatrix.m[0][0](最初の要素にアクセスする)を使用できること、またはmyMatrix._11を使用して同じ要素にアクセスできることです。 ユニオン は秘密です。

4
bobobobo
#include<cstdio>
template <size_t N, size_t M>
struct DataHolder
{
    int data[N][M];
    DataHolder()
    {
       for(int i=0; i<N; ++i)
           for(int j=0; j<M; ++j)
               data[i][j] = 0;
    }
};

template <size_t N, size_t M>
void print(const DataHolder<N,M>& dataHolder) {
    printf("\n");
    for(int i = 0; i<N; i++) {
        for(int j = 0; j<M; j++) {
            printf("%d, ", dataHolder.data[i][j]);
        }
    }
    printf("\n");
}

int main() {
    DataHolder<4,4> a;
    print(a);
}
2
Notinlist

多次元配列は、連続したメモリブロックです。だから、あなたはこの方法でそれを行うことができます:

#include <stdio.h>

void pa(const int *a, int y, int x)
{
    int i, j;
    for (i=0;i<y;i++)
    {
        for (j=0;j<x;j++)
            printf("%i", *(a+j+i*x));
        printf("\n");
    }
}

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

    pa(a[0], 4, 3);

    return 0;
}

また、C++でも機能します。

1
it-west.net

C99で可変長配列を使用する以外に、実際にはportably配列のサイズが異なる場合に多次元配列を受け入れる関数を書くことはできませんコンパイル時に既知です。 C-FAQQuestion 6.19 を参照してください。これを処理する最良の方法は、動的に割り当てられたメモリを使用して多次元配列をシミュレートすることです。 Question 6.16 は、これを実行する詳細を非常にうまく説明しています。

1
Robert Gamble

最初に行うことは、型を正しく取得することです。 C++の規則が配列型に関してCの規則と同じである場合(そうだと確信しています)、宣言を与えます

_int a[4][4];
_

aの型は_int [4][4]_であり、printに渡されたときに暗黙的にint (*)[4](intの4要素配列へのポインター)のポインター型に変換(「減衰」)されるため、 printを変更するには

_void print(int (*arr)[4], int s1, int s2)
{
  int i, j;        
  for(i = 0; i<s1; i++)        
    for(j = 0; j<s2; j++)        
      printf("%d, ", arr[i][j]);        
}        
_

式_arr[i]_は暗黙的にarrを間接参照するため、明示的な間接参照を台無しにする必要はありません。

欠点は、printはintのNx4配列しか処理できないことです。他の配列サイズを処理する場合は、別のアプローチをとる必要があります。

配列を渡す代わりに、最初の要素のアドレスを渡し、printに手動でオフセットを計算させることができます。

_int main() {                    
  int a[4][4] = {{0}};                    
  print(&a[0][0],4,4);  // note how a is being passed                  
}  

void print(int *arr, int s1, int s2)  // note that arr is a simple int *
{
  int i, j;
  for (i = 0; i < s1; i++)
    for (j = 0; j < s2; j++)
      printf("%d, ", arr[i * s2 + j]);
}
_
0
John Bode
#include<stdio.h>
void print(int (*arr)[4], int s1, int s2) {
    int i, j;
    for(i = 0; i<s1; i++)
        for(j = 0; j<s2; j++)
            printf("%d, ", arr[i][j]);
}

int main() {
    int a[4][4] = {{6}};
    print(a,4,4);
}

これは編集をコンパイルします:誰かがこの解決策をすでに投稿しています

0
devmabbott

短い答え、あなたは次のようにプログラムを変更することができます

void print(int arr[], int s1, int s2) {
...
printf("%d,", *(a+i + s2*j));
...
print((int*)a,4,4);

これには、CとC++のポインターとポインター演算および配列の違いを説明するより良い答えが必要です。今は起動しません。たぶん他の誰か?

コード内の他のポスターと同じ点にショックを受けていないことは明らかです。印刷関数のヘッダーで最も気になるのは、初期ポインターを元に戻すつもりのない配列に二重間接指定を使用していることです(実際、定数であるため実行できません)。 @ | V | lad答えは、1つまたは2つの次元を固定定数に設定することでこれを修正しますが、s1とs2を渡すと無駄になります。

すべては、本当にやりたいことによって異なります。 printは汎用配列印刷関数ですか、それとも一部の配列タイプ専用の関数ですか?

0
kriss