パラメータとして可変サイズの2次元配列を取りたい関数があります。
これまでのところ私はこれを持っています:
void myFunction(double** myArray){
myArray[x][y] = 5;
etc...
}
そして私は自分のコードのどこかで配列を宣言しました:
double anArray[10][10];
ただし、myFunction(anArray)
を呼び出すとエラーが発生します。
渡すときに配列をコピーしたくありません。myFunction
name__に変更を加えると、anArray
name__の状態が変更されるはずです。私が正しく理解したなら、私は引数として2次元配列へのポインタを渡したいだけです。この関数は異なるサイズの配列も受け入れる必要があります。そのため、例えば[10][10]
や[5][5]
のようになります。これどうやってするの?
関数に2次元配列を渡すには3つの方法があります。
パラメータは2次元配列です
int array[10][10];
void passFunc(int a[][10])
{
// ...
}
passFunc(array);
パラメータはポインタを含む配列です。
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);
パラメータはポインタへのポインタです。
int **array;
array = new int *[10];
for(int i = 0; i <10; i++)
array[i] = new int[10];
void passFunc(int **a)
{
// ...
}
passFunc(array);
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)
で渡され、最初の要素のアドレスではなく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]
は許可されますが、上記の構文では識別子array
が10個の整数の配列への単一ポインターであることを明確にしているため、上記の構文よりもお勧めしません。 looks 2D配列のようですが、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**
}
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ステートメントは、配列が参照によって渡されていることを示すためにあります(変数のアドレスを表示することによって)
このように関数テンプレートを作成することができます。
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);
}
誰もこれに言及していないことに驚きましたが、[] []セマンティクスをサポートしている2Dなら何でも単純にテンプレート化できます。
template <typename TwoD>
void myFunction(TwoD& myArray){
myArray[x][y] = 5;
etc...
}
// call with
double anArray[10][10];
myFunction(anArray);
std::vector<std::vector<T>>
などの任意の2D「配列のような」データ構造、またはコードの再利用を最大化するためのユーザー定義型で機能します。
anArray[10][10]
はポインタへのポインタではありません。これは、double型の100個の値を格納するのに適した連続したメモリの塊です。あなたはそれを配列として関数に渡す必要があります。次のように、初期寸法のサイズを省略することができます。
void f(double p[][10]) {
}
しかし、これでは最後の次元が10以外の配列を渡すことはできません。
C++での最善の解決策はstd::vector<std::vector<double> >
を使うことです:それはほぼ同じくらい効率的で、そしてかなり便利です。
あなたはこのようなことをすることができます...
#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
一次元配列は、配列の最初の要素を指すポインタポインタに崩壊します。 2次元配列は最初の行を指すポインタに崩壊します。そのため、関数のプロトタイプは次のようになります -
void myFunction(double (*myArray) [10]);
私は生の配列よりもstd::vector
を好むでしょう。
これはベクトル行列の例のベクトルです
#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配列を型キャストする必要がある。
#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つの重要なことは、
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.単一のポインタを使用する :このメソッドでは、関数に渡すときに2次元配列を型キャストする必要があります。
`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;
}`
動的なサイズの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);
}
これを行うには、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)を作成します。
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);