web-dev-qa-db-ja.com

配列ポインタを返すか、パラメータとして挿入された配列にデータを入力しますか?

どちらが良いですか?後者は多くのCコードで使用されていることに気づきました。人々は通常、配列をmallocし、それをパラメーターとして関数に渡し、関数がそれを設定します。一方、Javaでは前者の方が人気があるようです。

一方が他方よりも優れていますか?そうでない場合、言語機能は設定を呼び出しますか?

2
user3724404

古典的なCでは、関数から配列を返すのはJavaほど簡単ではありません。そのため、C関数は呼び出し元から渡された配列にデータを入力することがよくありますが、Javaメソッドは通常、自然なデータフローに従います。つまり、結果を配列として返します。

したがって、Javaでは、自然なデータフローに従うことができます(つまり、そうする必要があります)。つまり、メソッドから新しい配列を返します。Cでは、直感に反していますが、呼び出し元に提供することをお勧めします配列-Cプログラマはそのスタイルに慣れています。

私の推論を説明しましょう:

Cスタイル

関数から配列を返したい場合は、配列を動的に割り当てる必要があります(関数を離れると存続しないため、スタックに置くことはできません。また、静的にすることもできません。複数の関数呼び出しを行うと、混同する)。呼び出し元にとって、それは彼が最終的に配列を解放する責任を持つことを意味します。

呼び出し元は配列の長さを知る必要があるため、無効な要素にアクセスしません。配列の結果として(例:int[])は、実際にはその要素型(int *)、長さを伝える場所がありません。

したがって、Cでは、関数から配列を返す簡単で自然な方法はありません。

(もちろん、それは古典的なCだけであり、それ以降のバージョンでは、古典的なCに欠けていた機能の多くが追加されましたが、多くのライブラリ関数はすでに初期に定義されていました...)。

Javaスタイル

配列は常に動的メモリ(ヒープ上)に作成されますが、これはJVMによって管理されます。したがって、呼び出し側はメモリを解放する義務はありません。これはガベージコレクタによって行われるためです。

Java配列は長さを知っているので、配列の長さにも問題はありません。

5
Ralf Kleberhoff

意味論的な観点から、メソッドから配列を返すことは、渡された配列とは異なる配列であることを示唆しています。そのため、配列を変更するときに受け入れられたCスタイル、渡された配列をの場所で変更します。newコレクションを返すことは、Java Cの場合よりも、戻り値として関数から渡されるのはこのためです。

Cの配列は、実際には型付き値への単なるポインタです。 Cは、配列を動的に割り当てた場合、配列の長ささえ知らないため、呼び出し側が返された配列を処理するのが困難になります。 Java=の配列には、配列の長さやそのメンバーのタイプなどのメタデータが含まれているため、メソッドが新しい配列を返したり、呼び出し元がそれらを簡単に使用したりできます。

Javaで既存の配列をそのまま変更することもできます。その場合、Javaで同じことを行います。 C:渡された配列パラメーターin-placeを変更します。

CポインターとJava参照は、同じような目的を達成するために使用されていても、同じものではありません。Cでは、ポインターは実際のメモリアドレスです。 ポインター演算はCで可能であり、実際にはメモリの割り当てと配列メンバーおよび構造体メンバーの逆参照に使用されます。

Javaでは、参照はメモリアドレスではなく、実装の詳細です。それらに対してポインタ演算を実行することはできません。 Java言語仕様はそれらがどのように実装されるかを規定していないので、それらはメモリの観点から内部で動作します。

この違いは、長さ0の配列の扱いでさらに強調されています。構造体の最後のメンバーを除いて、 Cには存在しない です。

5
Robert Harvey

言語機能は設定を呼び出しますか?

はいぜったいに。 Cが配列式を処理する方法のため、関数にパラメーターとして既に割り当てられている配列(静的、自動、また​​は動的)を渡すことは、配列に領域を割り当てるよりも far の方が望ましい呼び出し元へのポインタを返します。

まず、基本:

sizeof演算子または単項&演算子のオペランドである場合、または宣言内の別の配列を初期化するために使用される文字列リテラルである場合を除き、 expression タイプ "N- T "の要素配列は、タイプ" pointer to T "の式に変換(" decay ")され、式の値は配列の最初の要素のアドレスになります1。 IOW、配列式はほとんどの状況で「配列性」を失います。

次のような配列パラメーターを使用して関数を呼び出す場合

int arr[N];
...
foo( arr );

fooの呼び出しの expression arrは、タイプint [N]からint *に「減衰」し、fooが受け取るのは、配列ではなくポインタ値になります。オブジェクト:

void foo( int *arr ) // T a[N] and T a[] are treated as T *a
{                    // in a function parameter declaration
  ...
}

関数から配列 object を返すことはできません-Cでは、関数が配列型を返すことを許可していません(int foo()[10]のようなプロトタイプは許可されていません)。上記の変換規則により、return arr;は最初の要素へのポインタを返します。

int *foo( void )
{
  int arr[N];
  ...
  return arr; // equivalent to return &arr[0]
}

例外arr存在しなくなりましたfooが終了すると2、返されるポインタ値は invalid であり、逆参照しようとすると、未定義の動作が発生します。

この時点で、2つの選択肢があります-関数がターゲットバッファーを動的に割り当てて結果を呼び出し元に返すようにするか、呼び出し元がターゲットバッファーを割り当てて(動的かどうかにかかわらず)関数に渡すようにします。

標準Cでは、動的メモリの自動ガベージコレクションは一切定義されていないことに注意してください。すべての動的メモリは、freeの呼び出しによって明示的に割り当て解除する必要があります。

最初のオプションは、関数と呼び出し元の間でメモリ管理の役割を分割しますが、これは望ましくありません。関数は、関数が動的にメモリを割り当てていること、および関数を使い終わったときにそのメモリを解放する責任があることを認識するために、[////] has を持っています。関数のユーザーがドキュメントに注意を払わず、関数から返されたメモリを解放しないと、メモリリークが発生します。

2番目のオプションでは、すべてのメモリ管理を1つのパーティに任せます。呼び出された関数は、ポインタと最大バッファサイズを取得します-指し示されたメモリが静的に、自動的に、または動的に割り当てられるかどうかを心配する必要はありません。この方法はより安全で堅牢であるため、Cコードでより頻繁に使用されます。

Javaでは、配列は他のほとんどのデータ型と同じように扱われ(ポインター型に「減衰」しない)、関数が単純に呼び出し元に配列を返すようにする方が自然です。配列は作成時にヒープから割り当てられますが、完了時に割り当てを解除する必要はありません。参照カウントが0になると、ガベージコレクションが自動的に行われます。




  1. 「配列は単なるポインタです」はこのルールの誤った説明です。配列ポインタではありません。配列 expressions はほとんどの状況でポインター式に変換されますが、配列 object はポインターではありません。
  2. arrキーワードを使用してstaticを宣言すると、関数の終了後も保持されますが、コードは再入できなくなります。
0
John Bode