web-dev-qa-db-ja.com

関数パラメーターのint * vs int [N] vs int(*)[N]。どちらが良いと思いますか?

C(またはC++)でプログラミングする場合、配列を取る関数でパラメーターを指定する方法は3つあります。

以下は、C++の_std::accumulate_をCで実装した例です。

私はそれを次のように書くことができます:

_int accumulate(int n, int *array)
{
    int i;
    int sum = 0;
    for (i = 0; i < n; ++i) {
        sum += array[i];
    }
    return sum;
}
_

これはこれに書き込むこともできます(これはまったく同じことを意味します)。

_int accumulate(int n, int array[])
{
    int i;
    int sum = 0;
    for (i = 0; i < n; ++i) {
        sum += array[i];
    }
    return sum;
}
_

次のように書くこともできます:

_int accumulate(int n, int (*array)[])
{
    int i;
    int sum = 0;
    for (i = 0; i < n; ++i) {
        sum += (*array)[i];
    }
    return sum;
}
_

これらのオプションはすべて非常によく似ており、同じ実行可能コードを生成しますが、呼び出し元が引数を渡す方法が少し異なります。

これは、最初の2つのオプションが呼び出される方法です。

_int main(void)
{
    int a[] = {3, 4, 2, 4, 6, 1, -40, 23, 35};
    printf("%d\n", accumulate(ARRAY_LENGTH(a), a));
    return 0;
}
_

これは、thridオプションが呼び出される方法です。

_int main(void)
{
    int a[] = {3, 4, 2, 4, 6, 1, -40, 23, 35};
    printf("%d\n", accumulate(ARRAY_LENGTH(a), &a));
    return 0;
}
_

3番目のオプションでは、ユーザーが_&a_を使用してaのアドレスを明示的に指定する必要があることに注意してください。最初の2つのオプションでは、配列は暗黙的にCの同じ型へのポインターに変換されるため、これは必要ありません。

私は常に3番目のアプローチを優先してきました。

これが理由です:

  • 他の型がポインターによってどのように渡されるかにより一貫しています。

    _int draw_point(struct point *p);
    
    int main()
    {
        struct point p = {3, 4};
        draw_point(&p); // Here is the 'address of' operator required.
    }
    _
  • _ARRAY_LENGTH_のようなマクロを使用して、配列内の要素の量を取得することができます。

    _#include <stdio.h>
    #define ARRAY_LENGTH(A) (sizeof(A) / sizeof(A[0]))
    
    void this_works(int (*array)[10])
    {
        /* This works! */
        printf("%d\n", ARRAY_LENGTH(*array));
    }
    
    void this_is_invalid_and_dangerous(int array[10])
    {
        /* This does NOT work because `array` is actually a pointer. */
        printf("%d\n", ARRAY_LENGTH(array));
    }
    _

_int array[]_(および_int *array_)で私が目にする唯一の利点は、インデックスを取得するときに_array[X]_ではなく_(*array)[X]_を記述できることです。

これについての専門家の考えは何ですか?どのアプローチがより良いと思いますか、それはなぜですか?2つを混在させることはありますか?そうであれば、何を選びますか?

私が言ったように、私は常にint (*array)[N]を使用することを好みましたが、他の2つのアプローチも非常に一般的であることがわかります。

5
wefwefa3

実際には、

int accumulate( int n, int *array)

よく。これは最も柔軟であり(さまざまなサイズの配列を処理できます)、内部で何が起こっているかを最も厳密に反映します。

見えない

int accumulate( int (*array)[N] )

これは、特定の配列サイズを想定しているためです(サイズmustを指定する必要があります)。

コンパイラが可変長配列構文をサポートしている場合は、

int accumulate( size_t n, int (*array)[n] )

しかし、それがどれほど一般的であるかはわかりません。

7
John Bode

Cでは、配列表記が関数パラメーターに使用されると、自動的にポインター宣言に変換されるため、パラメーターをint * arrayおよびint array []として宣言します。同等。関数が配列を引数として期待することがより明確であるため、私は2番目のものを使用する傾向があります。

関数パラメーターをint(* array)[]として宣言することは、前の2つと同等ではなく、int **に変換され、パラメーターの値自体が関数で変更される可能性がある場合に使用する必要があります。配列の再割り当て。

3
vasicbre

CとC++には3つの方法があると言いますが、C++は実際には4番目の方法を利用可能にします。

template<std::size_t n>
void arrayFunction(std::array<int,n> &array)
{ ...}

これには、提案するソリューションに比べていくつかの利点があります。

  • 配列のサイズのパラメーターは、コンパイラーでの使用時に自動的に決定されます。つまり、指定する必要はありません。
  • サイズはコンパイル時定数です。つまり、コンパイラーは、提案されたバージョンでは不可能な多くの最適化を実行できます。
  • サイズは配列のタイプの一部であるため、誤ったサイズを指定することはできません
2
Jules

最初の宣言では、関数を別の方法で記述することもできます。

int accumulate(int n, int *array)
{
    int sum = 0;
    while (n-- > 0) sum += *array++;
    return sum;
}

したがって、変数iは必要ありません。

コードベースの慣用的なものが優先され、理解しやすいものが続き、最もパフォーマンスが高いものが少し離れています。 2番目の宣言は、理解するのが最も簡単です。

2
John Bickers