web-dev-qa-db-ja.com

配列を値で関数に渡せないのはなぜですか?

どうやら、複雑なクラスのインスタンスを関数に渡すことができますが、なぜ配列を関数に渡せないのでしょうか?

43
Alcott

起源は歴史的です。問題は、「関数に渡されると配列がポインターに減衰する」というルールが単純なことです。

配列のコピーは複雑で、あまり明確ではありません。これは、異なるパラメーターと異なる関数宣言で動作が変わるためです。

値による間接パスを引き続き実行できることに注意してください。

struct A { int arr[2]; };
void func(struct A);
55
Let_Me_Be

別の観点を次に示します。Cには単一の「配列」型はありません。むしろ、_T[N]_はNごとにdifferent型です。したがって、_T[1]_、_T[2]_などはすべて異なるタイプです。

Cでは関数のオーバーロードはありません。したがって、許可できる唯一の賢明なことは、単一タイプの配列を取る(または返す)関数です。

_void foo(int a[3]);  // hypothetical
_

おそらく、それはすべての配列を最初の要素へのポインターに減衰させ、ユーザーに他の手段でサイズを伝える必要があるという実際の決定よりもはるかに有用性が低いと考えられただけです。結局のところ、上記は次のように書き換えることができます。

_void foo(int * a)
{
  static const unsigned int N = 3;
  /* ... */
}
_

したがって、表現力を失うことはありませんが、一般性が大幅に向上します。

これはC++でも違いはありませんが、テンプレート駆動型のコード生成により、テンプレート関数foo(T (&a)[N])を記述できます。ここで、Nは推論されますが、これは単にNの各値に1つずつ、distinct、different関数のファミリ全体を作成できます。

極端な場合として、2つの関数print6(const char[6])print12(const char[12])が必要で、print6("Hello")print12("Hello World")が必要ない場合は、配列をポインターに減衰させるか、そうでなければ明示的な変換print_p((const char*)"Hello World")を追加する必要があります。

27
Kerrek SB

非常に古い質問に答えます。QuestionはC++が補完目的で追加された市場であるため、std :: arrayを使用して、値または参照によって関数に配列を渡すことができます。

以下はサンプルです:

#include <iostream>
#include <array>

//pass array by reference
template<size_t N>
void fill_array(std::array<int, N>& arr){
    for(int idx = 0; idx < arr.size(); ++idx)
        arr[idx] = idx*idx;
}

//pass array by value
template<size_t N>
void print_array(std::array<int, N> arr){
    for(int idx = 0; idx < arr.size(); ++idx)
        std::cout << arr[idx] << std::endl;
}

int main()
{
    std::array<int, 5> arr;
    fill_array(arr);
    print_array(arr);
    //use different size
    std::array<int, 10> arr2;
    fill_array(arr2);
    print_array(arr2);
}
7
Ayub

値で配列を渡すことができない理由は、配列のサイズを追跡する特定の方法がないためです。そのため、関数呼び出しロジックは、割り当てるメモリの量とコピーする対象を知ることができます。クラスにはコンストラクターがあるため、クラスインスタンスを渡すことができます。配列はそうではありません。

5
David Schwartz

夏らしい:

  1. 配列の最初の要素のAddressを渡す&a = a = &(a[0])
  2. 新しいポインター(新しいポインター、新しいアドレス、4バイト、メモリ)
  3. 同じメモリ位置を指す、異なるタイプ.

例1:

void by_value(bool* arr) // pointer_value passed by value
{
    arr[1] = true;
    arr = NULL; // temporary pointer that points to original array
}

int main()
{
    bool a[3] = {};
    cout << a[1] << endl; // 0
    by_value(a);
    cout << a[1] << endl; // 1 !!! 
}

アドレス:

[main] 
     a = 0046FB18 // **Original**
     &a = 0046FB18 // **Original**
[func]
     arr = 0046FB18 // **Original**
     &arr = 0046FA44 // TempPTR
[func]
     arr = NULL
     &arr = 0046FA44 // TempPTR

例2:

void by_value(bool* arr) 
{
    cout << &arr << arr; // &arr != arr
}

int main()
{
    bool a[3] = {};
    cout << &a << a; // &a == a == &a[0]
    by_value(arr);
}

アドレス

Prints: 
[main] 0046FB18 = 0046FB18
[func] 0046FA44 != 0046FB18

注意してください:

  1. &(required-lvalue):左辺値-to->右辺値
  2. Array Decay:新しいポインター(一時)は(値による)配列アドレスを指します

readmore:

右辺値

配列の減衰

1
Almog

これに相当するのは、最初に配列のコピーを作成し、それを関数に渡すことです(大きな配列では非常に効率が悪い場合があります)。

それ以外は、歴史的な理由によると言います。つまり、Cの値で配列を渡すことはできませんでした。

私の推測では、C++で値による配列の受け渡しを行わない理由は、オブジェクトは配列と比べて適度なサイズであると考えられていたからだと思います。

Delnanが指摘したように、std::vector実際には、配列のようなオブジェクトを値で関数に渡すことができます。

0
Andre Holzner

あなたはare値渡し:配列へのポインタの値。 Cで角括弧表記を使用することは、ポインタを間接参照するための単純な略記であることに注意してください。 ptr [2]は*(ptr + 2)を意味します。

角かっこを削除すると、配列へのポインターが取得されます。このポインターは、値によって関数に渡すことができます。

int x[2] = {1, 2};
int result;
result = DoSomething(x);

ANSI C仕様の タイプのリスト を参照してください。配列はプリミティブ型ではありませんが、ポインターと演算子の組み合わせから構成されます。 (別のリンクを配置することはできませんが、構築については「配列型の派生」で説明しています。)

0
KonradG

配列が物理ポインターとして実装されているB言語との構文およびセマンティックの互換性を維持するために、この方法で行われました。

この質問に対する直接的な答えは、デニスリッチーの 「C言語の開発」 にあります。「批評」セクションを参照してください。それは言う

たとえば、関数宣言の空の角括弧

int f(a) int a[]; { ... }

nBのポインター宣言方法の名残である生きている化石です。 aは、この特別な場合に限り、Cでポインターとして解釈されます。表記法は互換性のために一部は生き残りましたが、一部には、プログラマが単一の参照ではなく配列から生成されたポインタをfに渡す意図を読者に伝えることができるという合理化の下で整数。残念ながら、それは学習者を混乱させるだけでなく、読者に警告するのにも役立ちます。

これは、記事の前の部分、特に「Embryonic C」のコンテキストでとるべきです。これは、Cにstruct型を導入すると、配列の実装に対するBおよびBCPLスタイルのアプローチが拒否された方法を説明しています(すなわち、通常のポインタとして)。 Cは、関数パラメーターリストでのみレガシーBスタイルのセマンティクスを維持し、非ポインター配列の実装に切り替えました。

したがって、配列パラメーターの動作の現在のバリアントは妥協の結果です。一方で、コピー可能な配列をstructsに持たなければなりませんでしたが、一方で、 B、配列は常に「ポインタ」で渡されます。

0
AnT