固定サイズ
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]
許可されている上記の構文は、それが識別子があることが明らかになり以来、私は上記の構文上でそれをお勧めしませんしたいarray
10個の整数の配列への単一のポインタで、この構文ながら、ルックス、それは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**
}